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 <wojciech.mista@hotmail.com>
This commit is contained in:
Michał Droń 2022-02-02 09:48:12 +01:00 committed by GitHub
parent f9e1bb0569
commit 519b816afa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 211 additions and 99 deletions

View file

@ -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"

View file

@ -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<CollectionListProps> = props => {
selectedChannelId,
toggle,
toggleAll,
toolbar
toolbar,
filterDependency
} = props;
const classes = useStyles(props);
const intl = useIntl();
return (
<ResponsiveTable>
@ -111,7 +115,7 @@ const CollectionList: React.FC<CollectionListProps> = props => {
>
<FormattedMessage defaultMessage="No. of Products" />
</TableCellHeader>
<TableCellHeader
<TooltipTableCellHeader
direction={
sort.sort === CollectionListUrlSortField.available
? getArrowDirection(sort.asc)
@ -125,12 +129,15 @@ const CollectionList: React.FC<CollectionListProps> = props => {
!!selectedChannelId
)
}
tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, {
filterName: filterDependency.label
})}
>
<FormattedMessage
defaultMessage="Availability"
description="collection availability"
/>
</TableCellHeader>
</TooltipTableCellHeader>
</TableHead>
<TableFooter>
<TableRow>

View file

@ -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<CollectionListPageProps> = ({
const intl = useIntl();
const filterStructure = createFilterStructure(intl, filterOpts);
const filterDependency = filterStructure.find(getByName("channel"));
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.collections)}>
@ -97,6 +100,7 @@ const CollectionListPage: React.FC<CollectionListPageProps> = ({
disabled={disabled}
channelsCount={channelsCount}
selectedChannelId={selectedChannelId}
filterDependency={filterDependency}
{...listProps}
/>
</Card>

View file

@ -63,61 +63,64 @@ export interface TableCellHeaderProps extends TableCellProps {
disabled?: boolean;
}
const TableCellHeader: React.FC<TableCellHeaderProps> = props => {
const classes = useStyles(props);
const {
arrowPosition,
children,
className,
direction,
textAlign,
disabled = false,
onClick,
title,
...rest
} = props;
const TableCellHeader = React.forwardRef<unknown, TableCellHeaderProps>(
(props, ref) => {
const classes = useStyles(props);
const {
arrowPosition,
children,
className,
direction,
textAlign,
disabled = false,
onClick,
title,
...rest
} = props;
return (
<TableCell
{...rest}
onClick={e => {
if (disabled || !onClick) {
e.preventDefault();
} else {
onClick(e);
}
}}
className={classNames(classes.root, className, {
[classes.disabled]: disabled,
[classes.notSortable]: !onClick
})}
>
<div
className={classNames(classes.labelContainer, {
[classes.labelContainerActive]: !!direction && !!arrowPosition,
[classes.labelContainerCenter]: textAlign === "center",
[classes.labelContainerRight]: textAlign === "right"
return (
<TableCell
{...rest}
innerRef={ref}
onClick={e => {
if (disabled || !onClick) {
e.preventDefault();
} else {
onClick(e);
}
}}
className={classNames(classes.root, className, {
[classes.disabled]: disabled,
[classes.notSortable]: !onClick
})}
>
{!!direction && arrowPosition === "left" && (
<ArrowSort
className={classNames(classes.arrow, classes.arrowLeft, {
[classes.arrowUp]: direction === "asc"
})}
/>
)}
<div className={classes.label}>{children}</div>
{!!direction && arrowPosition === "right" && (
<ArrowSort
className={classNames(classes.arrow, {
[classes.arrowUp]: direction === "asc"
})}
/>
)}
</div>
</TableCell>
);
};
<div
className={classNames(classes.labelContainer, {
[classes.labelContainerActive]: !!direction && !!arrowPosition,
[classes.labelContainerCenter]: textAlign === "center",
[classes.labelContainerRight]: textAlign === "right"
})}
>
{!!direction && arrowPosition === "left" && (
<ArrowSort
className={classNames(classes.arrow, classes.arrowLeft, {
[classes.arrowUp]: direction === "asc"
})}
/>
)}
<div className={classes.label}>{children}</div>
{!!direction && arrowPosition === "right" && (
<ArrowSort
className={classNames(classes.arrow, {
[classes.arrowUp]: direction === "asc"
})}
/>
)}
</div>
</TableCell>
);
}
);
TableCellHeader.displayName = "TableCellHeader";
TableCellHeader.defaultProps = {

View file

@ -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<TooltipTableCellHeaderProps> = props => {
const { children, tooltip, disabled, ...rest } = props;
const tooltipDisabled = () => {
if (!tooltip) {
return true;
}
return !disabled;
};
return (
<Tooltip title={tooltip} placement="top" disabled={tooltipDisabled()}>
<TableCellHeader disabled={disabled} {...rest}>
{children}
</TableCellHeader>
</Tooltip>
);
};
TooltipTableCellHeader.displayName = "TooltipTableCellHeader";
export default TooltipTableCellHeader;

View file

@ -0,0 +1,2 @@
export { default } from "./TooltipTableCellHeader";
export * from "./TooltipTableCellHeader";

View file

@ -0,0 +1,8 @@
import { defineMessages } from "react-intl";
export const commonTooltipMessages = defineMessages({
noFilterSelected: {
defaultMessage:
"Sorting by this column requires active filter: {filterName}"
}
});

View file

@ -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<SaleListProps> = 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<SaleListProps> = props => {
>
<FormattedMessage defaultMessage="Ends" description="sale end date" />
</TableCellHeader>
<TableCellHeader
<TooltipTableCellHeader
direction={
sort.sort === SaleListUrlSortField.value
? getArrowDirection(sort.asc)
@ -149,10 +153,13 @@ const SaleList: React.FC<SaleListProps> = props => {
disabled={
!canBeSorted(SaleListUrlSortField.value, !!selectedChannelId)
}
tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, {
filterName: filterDependency.label
})}
className={classes.colValue}
>
<FormattedMessage defaultMessage="Value" description="sale value" />
</TableCellHeader>
</TooltipTableCellHeader>
</TableHead>
<TableFooter>
<TableRow>

View file

@ -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<SaleListPageProps> = ({
const intl = useIntl();
const structure = createFilterStructure(intl, filterOpts);
const filterDependency = structure.find(getByName("channel"));
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.sales)}>
@ -78,7 +81,7 @@ const SaleListPage: React.FC<SaleListPageProps> = ({
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<SaleList {...listProps} />
<SaleList filterDependency={filterDependency} {...listProps} />
</Card>
</Container>
);

View file

@ -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<VoucherListProps> = props => {
sort,
toggle,
toggleAll,
toolbar
toolbar,
filterDependency
} = props;
const classes = useStyles(props);
const intl = useIntl();
return (
<ResponsiveTable>
@ -128,7 +132,7 @@ const VoucherList: React.FC<VoucherListProps> = props => {
>
<FormattedMessage defaultMessage="Code" description="voucher code" />
</TableCellHeader>
<TableCellHeader
<TooltipTableCellHeader
direction={
sort.sort === VoucherListUrlSortField.minSpent
? getArrowDirection(sort.asc)
@ -140,12 +144,15 @@ const VoucherList: React.FC<VoucherListProps> = props => {
!canBeSorted(VoucherListUrlSortField.minSpent, !!selectedChannelId)
}
className={classes.colMinSpent}
tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, {
filterName: filterDependency.label
})}
>
<FormattedMessage
defaultMessage="Min. Spent"
description="minimum amount of spent money to activate voucher"
/>
</TableCellHeader>
</TooltipTableCellHeader>
<TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.startDate
@ -176,7 +183,7 @@ const VoucherList: React.FC<VoucherListProps> = props => {
description="voucher is active until date"
/>
</TableCellHeader>
<TableCellHeader
<TooltipTableCellHeader
direction={
sort.sort === VoucherListUrlSortField.value
? getArrowDirection(sort.asc)
@ -188,12 +195,15 @@ const VoucherList: React.FC<VoucherListProps> = props => {
!canBeSorted(VoucherListUrlSortField.minSpent, !!selectedChannelId)
}
className={classes.colValue}
tooltip={intl.formatMessage(commonTooltipMessages.noFilterSelected, {
filterName: filterDependency.label
})}
>
<FormattedMessage
defaultMessage="Value"
description="voucher value"
/>
</TableCellHeader>
</TooltipTableCellHeader>
<TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.limit

View file

@ -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 { VoucherListUrlSortField } from "@saleor/discounts/urls";
@ -50,6 +51,8 @@ const VoucherListPage: React.FC<VoucherListPageProps> = ({
const intl = useIntl();
const structure = createFilterStructure(intl, filterOpts);
const filterDependency = structure.find(getByName("channel"));
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.vouchers)}>
@ -80,7 +83,7 @@ const VoucherListPage: React.FC<VoucherListPageProps> = ({
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<VoucherList {...listProps} />
<VoucherList filterDependency={filterDependency} {...listProps} />
</Card>
</Container>
);

View file

@ -164,7 +164,7 @@ export function getFilterQueryParam(
}
}
const messages = defineMessages({
export const messages = defineMessages({
balanceAmountLabel: {
defaultMessage: "Amount",
description: "amount filter label"

View file

@ -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<GiftCardsListTableHeaderProps> = ({
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: <b>{filterLabels.currencyLabel.defaultMessage}</b>
})
};
const { title, ...headerTooltipItemProps } = headerTooltipItem;
return (
<>
<colgroup>
@ -105,18 +116,22 @@ const GiftCardsListTableHeader: React.FC<GiftCardsListTableHeaderProps> = ({
</div>
}
>
{headerItems.map(
({ title, options, onClick, direction, canBeSorted = true }) => (
<TableCellHeader
{...options}
onClick={onClick}
disabled={!canBeSorted}
direction={direction}
>
<Label text={intl.formatMessage(title)} size={LabelSizes.md} />
</TableCellHeader>
)
)}
{headerItems.map(({ title, options, onClick, direction }) => (
<TableCellHeader
{...options}
onClick={onClick}
direction={direction}
key={title.defaultMessage}
>
<Label text={intl.formatMessage(title)} size={LabelSizes.md} />
</TableCellHeader>
))}
<TooltipTableCellHeader {...headerTooltipItemProps}>
<Label
text={intl.formatMessage(headerTooltipItem.title)}
size={LabelSizes.md}
/>
</TooltipTableCellHeader>
<TableCell className={classes.colDelete} />
</TableHead>
</>

View file

@ -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<ProductListProps> = 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<ProductListProps> = props => {
column="availability"
displayColumns={settings.columns}
>
<TableCellHeader
<TooltipTableCellHeader
data-test-id="colAvailabilityHeader"
className={classes.colPublished}
direction={
@ -230,9 +234,13 @@ export const ProductList: React.FC<ProductListProps> = props => {
!!selectedChannelId
)
}
tooltip={intl.formatMessage(
commonTooltipMessages.noFilterSelected,
{ filterName: filterDependency.label }
)}
>
<FormattedMessage {...columnsMessages.availability} />
</TableCellHeader>
</TooltipTableCellHeader>
</DisplayColumn>
{gridAttributesFromSettings.map(gridAttributeFromSettings => {
const attributeId = getAttributeIdFromColumnValue(
@ -278,7 +286,7 @@ export const ProductList: React.FC<ProductListProps> = props => {
</TableCellHeader>
</DisplayColumn>
<DisplayColumn column="price" displayColumns={settings.columns}>
<TableCellHeader
<TooltipTableCellHeader
data-test-id="colPriceHeader"
className={classes.colPrice}
direction={
@ -291,9 +299,13 @@ export const ProductList: React.FC<ProductListProps> = props => {
disabled={
!canBeSorted(ProductListUrlSortField.price, !!selectedChannelId)
}
tooltip={intl.formatMessage(
commonTooltipMessages.noFilterSelected,
{ filterName: filterDependency.label }
)}
>
<FormattedMessage {...columnsMessages.price} />
</TableCellHeader>
</TooltipTableCellHeader>
</DisplayColumn>
</TableHead>
<TableFooter>

View file

@ -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<ProductListPageProps> = 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<ProductListPageProps> = props => {
channelsCount={channelsCount}
selectedChannelId={selectedChannelId}
onUpdateListSettings={onUpdateListSettings}
filterDependency={filterDependency}
/>
</Card>
</Container>

View file

@ -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<TColumns extends string = string> {
value: ListSettings<TColumns>[T]
) => void;
onListSettingsReset?: () => void;
filterDependency?: IFilterElement;
}
export interface SortPage<TSortKey extends string> {