Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Table): update ActionsColumn to use Dropdown next #8629

Merged
merged 8 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export class DropdownToggleCheckbox extends React.Component<DropdownToggleCheckb
const spinner = (
<Spinner
diameter="1em"
isSVG
aria-valuetext={defaultProgressAriaValueText}
aria-live="polite"
aria-label={defaultProgressAriaLabel}
Expand Down
8 changes: 6 additions & 2 deletions packages/react-core/src/next/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { css } from '@patternfly/react-styles';
import { Menu, MenuContent, MenuProps } from '../../../components/Menu';
import { Popper } from '../../../helpers/Popper/Popper';
import { Popper, PopperProps } from '../../../helpers/Popper/Popper';
import { useOUIAProps, OUIAProps } from '../../../helpers';

export interface DropdownProps extends MenuProps, OUIAProps {
Expand Down Expand Up @@ -32,6 +32,8 @@ export interface DropdownProps extends MenuProps, OUIAProps {
ouiaSafe?: boolean;
/** z-index of the dropdown menu */
zIndex?: number;
/** Additional properties to pass to the Popper */
popperProps?: PopperProps;
}

const DropdownBase: React.FunctionComponent<DropdownProps> = ({
Expand All @@ -48,6 +50,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
ouiaId,
ouiaSafe = true,
zIndex = 9999,
popperProps,
...props
}: DropdownProps) => {
const localMenuRef = React.useRef<HTMLDivElement>();
Expand Down Expand Up @@ -100,7 +103,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
<Menu
className={css(className)}
ref={menuRef}
onSelect={(event, itemId) => onSelect(event, itemId)}
onSelect={(event, itemId) => onSelect && onSelect(event, itemId)}
isPlain={isPlain}
isScrollable={isScrollable}
{...(minWidth && {
Expand All @@ -121,6 +124,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
appendTo={containerRef.current || undefined}
isVisible={isOpen}
zIndex={zIndex}
{...popperProps}
/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-core/src/next/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
role={role}
className={css(className)}
ref={menuRef}
onSelect={(event, itemId) => onSelect(event, itemId)}
onSelect={(event, itemId) => onSelect && onSelect(event, itemId)}
isPlain={isPlain}
selected={selected}
{...(minWidth && {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';

import { isCustomWizardFooter, isWizardSubStep, WizardStepType, WizardFooterType } from './types';
import { getActiveStep } from './utils';
import { WizardFooter, WizardFooterProps } from './WizardFooter';

export interface WizardContextProps {
Expand Down Expand Up @@ -76,7 +75,8 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide
})),
[initialSteps, currentSteps]
);
const activeStep = React.useMemo(() => getActiveStep(steps, activeStepIndex), [activeStepIndex, steps]);

const activeStep = React.useMemo(() => steps.find(step => step.index === activeStepIndex), [activeStepIndex, steps]);

const close = React.useCallback(() => onClose?.(null), [onClose]);
const goToNextStep = React.useCallback(() => onNext(null, steps), [onNext, steps]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-i
import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon';

import { KeyTypes } from '../../../helpers/constants';
import { WizardNavProps, WizardBody, WizardStep, WizardStepProps } from '../Wizard';
import { WizardStepType, isWizardSubStep } from './types';
import { WizardNavProps } from './WizardNav';
import { WizardStep, WizardStepProps } from './WizardStep';
import { WizardBody } from './WizardBody';

/**
* Used to toggle between step content, including the body and footer. This is also where the navigation and its expandability is controlled.
Expand Down
3 changes: 0 additions & 3 deletions packages/react-core/src/next/components/Wizard/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,3 @@ export const normalizeStepProps = ({
steps: _steps,
...controlStep
}: WizardStepProps): Omit<WizardStepType, 'index'> => controlStep;

export const getActiveStep = (steps: WizardStepType[], activeStepIndex: number) =>
steps.find(step => step.index === activeStepIndex);
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ describe('Table Simple Actions Test', () => {
});

it('Verify dropdown toggle', () => {
cy.get('td .pf-c-dropdown__toggle')
cy.get('td .pf-c-menu-toggle')
.first()
.should('exist');
cy.get('td .pf-c-dropdown__toggle')
cy.get('td .pf-c-menu-toggle')
.first()
.click();
cy.get('.pf-c-dropdown__menu').should('exist');
cy.get('.pf-c-dropdown__menu-item')
cy.get('.pf-c-menu').should('exist');
cy.get('.pf-c-menu__item')
.first()
.click();
});
Expand Down
195 changes: 92 additions & 103 deletions packages/react-table/src/components/Table/ActionsColumn.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,59 @@
import * as React from 'react';
import { Dropdown } from '@patternfly/react-core/dist/esm/components/Dropdown';
import { KebabToggle } from '@patternfly/react-core/dist/esm/components/Dropdown/KebabToggle';
import { DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown/DropdownItem';
import { DropdownSeparator } from '@patternfly/react-core/dist/esm/components/Dropdown/DropdownSeparator';
import { Button } from '@patternfly/react-core/dist/esm/components/Button/Button';

import {
DropdownDirection,
DropdownPosition
} from '@patternfly/react-core/dist/esm/components/Dropdown/dropdownConstants';

import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/dist/esm/next/components';
import { Button } from '@patternfly/react-core/dist/esm/components/Button';
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle';
import { IAction, IExtraData, IRowData } from './TableTypes';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';

export interface CustomActionsToggleProps {
onToggle: (isOpen: boolean) => void;
onToggle: (evt: React.MouseEvent) => void;
isOpen: boolean;
isDisabled: boolean;
toggleRef: React.Ref<any>;
}

export interface ActionsColumnProps {
children?: React.ReactNode;
export interface ActionsColumnProps extends Omit<React.HTMLProps<HTMLElement>, 'label'> {
/** Actions to be rendered within or without the action dropdown */
items: IAction[];
/** Indicates whether the actions dropdown is disabled */
isDisabled?: boolean;
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
dropdownPosition?: DropdownPosition;
dropdownDirection?: DropdownDirection;
/** Data of the row the action dropdown is located */
rowData?: IRowData;
/** Extra data of a row */
extraData?: IExtraData;
/** Custom actions toggle for the actions dropdown */
actionsToggle?: (props: CustomActionsToggleProps) => React.ReactNode;
/** Additional properties for the actions dropdown popper */
popperProps?: any;
/** @hide Forwarded ref */
innerRef?: React.Ref<any>;
}

export interface ActionsColumnState {
isOpen: boolean;
}

export class ActionsColumn extends React.Component<ActionsColumnProps, ActionsColumnState> {
static displayName = 'ActionsColumn';
private toggleRef = React.createRef<HTMLButtonElement>();
static defaultProps = {
children: null as React.ReactNode,
items: [] as IAction[],
dropdownPosition: DropdownPosition.right,
dropdownDirection: DropdownDirection.down,
menuAppendTo: 'inline',
rowData: {} as IRowData,
extraData: {} as IExtraData
};
constructor(props: ActionsColumnProps) {
super(props);
this.state = {
isOpen: false
};
}
const ActionsColumnBase: React.FunctionComponent<ActionsColumnProps> = ({
items,
isDisabled,
rowData,
extraData,
actionsToggle,
popperProps = {
position: 'right',
direction: 'down',
popperMatchesTriggerWidth: false
},
...props
}: ActionsColumnProps) => {
const [isOpen, setIsOpen] = React.useState(false);

onToggle = (isOpen: boolean): void => {
this.setState({
isOpen
});
const onToggle = () => {
setIsOpen(!isOpen);
};

onClick = (
const onActionClick = (
event: React.MouseEvent<any> | React.KeyboardEvent | MouseEvent,
onClick:
| ((event: React.MouseEvent, rowIndex: number | undefined, rowData: IRowData, extraData: IExtraData) => void)
| undefined
): void => {
const { rowData, extraData } = this.props;
// Only prevent default if onClick is provided. This allows href support.
if (onClick) {
event.preventDefault();
Expand All @@ -74,62 +62,61 @@ export class ActionsColumn extends React.Component<ActionsColumnProps, ActionsCo
}
};

render() {
const { isOpen } = this.state;
const {
items,
children,
dropdownPosition,
dropdownDirection,
menuAppendTo,
isDisabled,
rowData,
actionsToggle
} = this.props;

const actionsToggleClone = actionsToggle ? (
actionsToggle({ onToggle: this.onToggle, isOpen, isDisabled })
) : (
<KebabToggle isDisabled={isDisabled} onToggle={this.onToggle} />
);
return (
<React.Fragment>
{items
.filter(item => item.isOutsideDropdown)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({ title, itemKey, onClick, isOutsideDropdown, ...props }, key) =>
typeof title === 'string' ? (
<Button
onClick={event => onActionClick(event, onClick)}
{...(props as any)}
isDisabled={isDisabled}
key={itemKey || `outside_dropdown_${key}`}
data-key={itemKey || `outside_dropdown_${key}`}
>
{title}
</Button>
) : (
React.cloneElement(title as React.ReactElement, { onClick, isDisabled, ...props })
)
)}

return (
<React.Fragment>
{items
.filter(item => item.isOutsideDropdown)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({ title, itemKey, onClick, isOutsideDropdown, ...props }, key) =>
typeof title === 'string' ? (
<Button
onClick={event => this.onClick(event, onClick)}
{...(props as any)}
isDisabled={isDisabled}
key={itemKey || `outside_dropdown_${key}`}
data-key={itemKey || `outside_dropdown_${key}`}
>
{title}
</Button>
) : (
React.cloneElement(title as React.ReactElement, { onClick, isDisabled, ...props })
)
)}
<Dropdown
toggle={actionsToggleClone}
position={dropdownPosition}
direction={dropdownDirection}
menuAppendTo={menuAppendTo}
isOpen={isOpen}
dropdownItems={items
<Dropdown
isOpen={isOpen}
onOpenChange={isOpen => setIsOpen(isOpen)}
toggle={toggleRef =>
actionsToggle ? (
actionsToggle({ onToggle, isOpen, isDisabled, toggleRef })
) : (
<MenuToggle
aria-label="Kebab toggle"
ref={toggleRef}
onClick={onToggle}
isExpanded={isOpen}
isDisabled={isDisabled}
variant="plain"
>
<EllipsisVIcon />
</MenuToggle>
)
}
{...(rowData && rowData.actionProps)}
{...props}
popperProps={popperProps}
>
<DropdownList>
{items
.filter(item => !item.isOutsideDropdown)
.map(({ title, itemKey, onClick, isSeparator, ...props }, key) =>
isSeparator ? (
<DropdownSeparator {...props} key={itemKey || key} data-key={itemKey || key} />
<Divider key={itemKey || key} data-key={itemKey || key} />
) : (
<DropdownItem
component="button"
onClick={event => {
this.onClick(event, onClick);
this.onToggle(!isOpen);
onActionClick(event, onClick);
onToggle();
}}
{...props}
key={itemKey || key}
Expand All @@ -139,11 +126,13 @@ export class ActionsColumn extends React.Component<ActionsColumnProps, ActionsCo
</DropdownItem>
)
)}
isPlain
{...(rowData && rowData.actionProps)}
/>
{children}
</React.Fragment>
);
}
}
</DropdownList>
</Dropdown>
</React.Fragment>
);
};

export const ActionsColumn = React.forwardRef((props: ActionsColumnProps, ref: React.Ref<HTMLElement>) => (
<ActionsColumnBase {...props} innerRef={ref} />
));
ActionsColumn.displayName = 'ActionsColumn';
3 changes: 2 additions & 1 deletion packages/react-table/src/components/Table/TableTypes.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DropdownItemProps } from '@patternfly/react-core/dist/esm/components/Dropdown/DropdownItem';
import { DropdownItemProps } from '@patternfly/react-core/next';
import { formatterValueType, ColumnType, RowType, RowKeyType, HeaderType } from './base';
import { SortByDirection } from './SortColumn';
import {
Expand Down Expand Up @@ -108,6 +108,7 @@ export interface IColumn {
dropdownDirection?: DropdownDirection;
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
actionsToggle?: (props: CustomActionsToggleProps) => React.ReactNode;
actionsPopperProps?: any;
allRowsSelected?: boolean;
allRowsExpanded?: boolean;
isHeaderSelectDisabled?: boolean;
Expand Down
Loading