Skip to content

Commit

Permalink
feat(Table): favoritable sortable header cell and example (#10823)
Browse files Browse the repository at this point in the history
* feat(Table): favoritable sortable header cell and example

* fix(TableFavoritable): better favorite / unfavorite state of the header favorite button

* refactor(TableFavoritable): custom aria-labels, refactoring
  • Loading branch information
adamviktora authored Sep 20, 2024
1 parent 9a679a4 commit 7eb9dfa
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 14 deletions.
28 changes: 28 additions & 0 deletions packages/react-table/src/components/Table/SortColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import * as React from 'react';
import LongArrowAltUpIcon from '@patternfly/react-icons/dist/esm/icons/long-arrow-alt-up-icon';
import LongArrowAltDownIcon from '@patternfly/react-icons/dist/esm/icons/long-arrow-alt-down-icon';
import ArrowsAltVIcon from '@patternfly/react-icons/dist/esm/icons/arrows-alt-v-icon';
import StarIcon from '@patternfly/react-icons/dist/esm/icons/star-icon';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Table/table';
import { TableText } from './TableText';
import { TooltipProps } from '@patternfly/react-core/dist/esm/components/Tooltip';
import { ActionList, ActionListItem, Button } from '@patternfly/react-core';
import { FavoriteButtonProps } from './base/types';

export enum SortByDirection {
asc = 'asc',
Expand All @@ -21,6 +24,7 @@ export interface SortColumnProps extends React.ButtonHTMLAttributes<HTMLButtonEl
tooltip?: React.ReactNode;
tooltipProps?: Omit<TooltipProps, 'content'>;
tooltipHasDefaultBehavior?: boolean;
favoriteButtonProps?: FavoriteButtonProps;
}

export const SortColumn: React.FunctionComponent<SortColumnProps> = ({
Expand All @@ -33,6 +37,7 @@ export const SortColumn: React.FunctionComponent<SortColumnProps> = ({
tooltip,
tooltipProps,
tooltipHasDefaultBehavior,
favoriteButtonProps,
...props
}: SortColumnProps) => {
let SortedByIcon;
Expand All @@ -42,6 +47,29 @@ export const SortColumn: React.FunctionComponent<SortColumnProps> = ({
} else {
SortedByIcon = ArrowsAltVIcon;
}

if (favoriteButtonProps) {
return (
<ActionList isIconList>
<ActionListItem>
<Button variant="plain" icon={<StarIcon />} {...favoriteButtonProps} />
</ActionListItem>
<ActionListItem>
<Button
variant="plain"
icon={
<span className={css(styles.tableSortIndicator)}>
<SortedByIcon />
</span>
}
onClick={(event) => onSort && onSort(event)}
{...props}
/>
</ActionListItem>
</ActionList>
);
}

return (
<button
{...props}
Expand Down
1 change: 1 addition & 0 deletions packages/react-table/src/components/Table/TableTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export interface IColumn {
allRowsExpanded?: boolean;
isHeaderSelectDisabled?: boolean;
onFavorite?: OnFavorite;
favoriteButtonProps?: ButtonProps;
};
}

Expand Down
9 changes: 7 additions & 2 deletions packages/react-table/src/components/Table/Th.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,18 @@ const ThBase: React.FunctionComponent<ThProps> = ({
}
onMouseEnterProp(event);
};

let sortParams = null;
if (sort) {
if (sort.isFavorites) {
sortParams = sortableFavorites({
onSort: sort?.onSort,
onSort: sort.onSort,
columnIndex: sort.columnIndex,
sortBy: sort.sortBy,
tooltip: tooltip as string,
tooltipProps
tooltipProps,
ariaLabel: sort['aria-label'],
favoriteButtonProps: sort.favoriteButtonProps
})();
} else {
sortParams = sortable(children as IFormatterValueType, {
Expand All @@ -137,6 +140,7 @@ const ThBase: React.FunctionComponent<ThProps> = ({
});
}
}

const selectParams = select
? selectable(children as IFormatterValueType, {
rowData: {
Expand Down Expand Up @@ -217,6 +221,7 @@ const ThBase: React.FunctionComponent<ThProps> = ({
hasRightBorder && scrollStyles.modifiers.borderRight,
hasLeftBorder && scrollStyles.modifiers.borderLeft,
modifier && styles.modifiers[modifier as 'breakWord' | 'fitContent' | 'nowrap' | 'truncate' | 'wrap'],
sort?.favoriteButtonProps?.favorited && styles.modifiers.favorited,
mergedClassName
)}
{...mergedProps}
Expand Down
13 changes: 10 additions & 3 deletions packages/react-table/src/components/Table/base/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
*/

import * as React from 'react';
import { TooltipProps } from '@patternfly/react-core/dist/esm/components/Tooltip';
import { PopoverProps } from '@patternfly/react-core/dist/esm/components/Popover';
import { SelectProps } from '@patternfly/react-core/dist/esm/components/Select';
import { ButtonProps, PopoverProps, SelectProps, TooltipProps } from '@patternfly/react-core';
import { Table } from '../Table';
import { Thead } from '../Thead';
import { Tbody } from '../Tbody';
Expand Down Expand Up @@ -156,15 +154,24 @@ export interface ThInfoType {
className?: string;
}

export interface FavoriteButtonProps extends ButtonProps {
/** Flag if the button is favorited. */
favorited?: boolean;
}

export interface ThSortType {
/** Wraps the content in a button and adds a sort icon - Click callback on the sortable cell */
onSort?: OnSort;
/** Provide the currently active column's index and direction */
sortBy: ISortBy;
/** The column index */
columnIndex: number;
/** Adds accessible text to the sort button. */
'aria-label'?: string;
/** True to make this a favoritable sorting cell */
isFavorites?: boolean;
/** Props for the favorite button (only for favoritable cell). */
favoriteButtonProps?: FavoriteButtonProps;
}

export interface ThSelectType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ export const TableFavoritable: React.FunctionComponent = () => {
// Index of the currently sorted column
// Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key
// as the identifier of the sorted column. See the "Compound expandable" example.
const [activeSortIndex, setActiveSortIndex] = React.useState<number | null>(null);
const [activeSortIndex, setActiveSortIndex] = React.useState<number>();

// Sort direction of the currently sorted column
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc'>();

// Favorite state is similar to selection state, see Selectable with checkbox.
const [favoriteRepoNames, setFavoriteRepoNames] = React.useState<string[]>([]);
Expand All @@ -42,6 +42,9 @@ export const TableFavoritable: React.FunctionComponent = () => {
});
const isRepoFavorited = (repo: Repository) => favoriteRepoNames.includes(repo.name);

// State of the header cell to favorite / unfavorite all rows
const [headerFavorited, setHeaderFavorited] = React.useState(false);

// Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
// In this example we only deal with booleans here because we only sort on the favorites column.
// For more complex sorting, see Sortable.
Expand All @@ -55,7 +58,7 @@ export const TableFavoritable: React.FunctionComponent = () => {
// Note that we perform the sort as part of the component's render logic and not in onSort.
// We shouldn't store the list of data in state because we don't want to have to sync that with props.
let sortedRepositories = repositories;
if (activeSortIndex !== null) {
if (activeSortIndex !== undefined) {
sortedRepositories = repositories.sort((a, b) => {
const aValue = getSortableRowValues(a)[activeSortIndex];
const bValue = getSortableRowValues(b)[activeSortIndex];
Expand All @@ -80,7 +83,16 @@ export const TableFavoritable: React.FunctionComponent = () => {
setActiveSortIndex(index);
setActiveSortDirection(direction);
},
columnIndex
'aria-label': 'Sort favorites',
columnIndex,
favoriteButtonProps: {
favorited: headerFavorited,
onClick: (_event) => {
repositories.forEach((repo) => setRepoFavorited(repo, !headerFavorited));
setHeaderFavorited(!headerFavorited);
},
'aria-label': headerFavorited ? 'Unfavorite all' : 'Favorite all'
}
});

return (
Expand All @@ -101,7 +113,20 @@ export const TableFavoritable: React.FunctionComponent = () => {
<Td
favorites={{
isFavorited: isRepoFavorited(repo),
onFavorite: (_event, isFavoriting) => setRepoFavorited(repo, isFavoriting),
onFavorite: (_event, isFavoriting) => {
setRepoFavorited(repo, isFavoriting);

if (
isFavoriting &&
repositories.filter((r) => r !== repo).every((r) => favoriteRepoNames.includes(r.name))
) {
setHeaderFavorited(true);
}

if (!isFavoriting && favoriteRepoNames.length === 1 && favoriteRepoNames.includes(repo.name)) {
setHeaderFavorited(false);
}
},
rowIndex
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import StarIcon from '@patternfly/react-icons/dist/esm/icons/star-icon';
export const sortableFavorites = (sort: any) => () =>
sortable(<StarIcon aria-hidden />, {
columnIndex: sort.columnIndex,
className: styles.modifiers.favorite,
ariaLabel: 'Sort favorites',
className: styles.tableFavorite,
ariaLabel: sort.ariaLabel ?? 'Sort favorites',
column: {
extraParams: {
sortBy: sort.sortBy,
onSort: sort?.onSort
onSort: sort.onSort,
favoriteButtonProps: sort.favoriteButtonProps
}
},
tooltip: sort.tooltip,
Expand All @@ -26,7 +27,7 @@ export const sortable: ITransform = (
{ columnIndex, column, property, className, ariaLabel, tooltip, tooltipProps, tooltipHasDefaultBehavior }: IExtra
) => {
const {
extraParams: { sortBy, onSort }
extraParams: { sortBy, onSort, favoriteButtonProps }
} = column;

const extraData = {
Expand Down Expand Up @@ -62,6 +63,7 @@ export const sortable: ITransform = (
tooltip={tooltip}
tooltipProps={tooltipProps}
tooltipHasDefaultBehavior={tooltipHasDefaultBehavior}
favoriteButtonProps={favoriteButtonProps}
>
{label as React.ReactNode}
</SortColumn>
Expand Down

0 comments on commit 7eb9dfa

Please sign in to comment.