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(templates): toggle props & improvements #10473

Merged
merged 6 commits into from
Jun 20, 2024
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 @@ -3,13 +3,14 @@ import {
Dropdown,
DropdownItem,
DropdownList,
DropdownItemProps
DropdownItemProps,
DropdownProps
} from '@patternfly/react-core/dist/esm/components/Dropdown';
import { MenuToggle, MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle';
import { MenuToggle, MenuToggleElement, MenuToggleProps } from '@patternfly/react-core/dist/esm/components/MenuToggle';
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
import { OUIAProps } from '@patternfly/react-core/dist/esm/helpers';

export interface DropdownSimpleItem extends Omit<DropdownItemProps, 'content'> {
export interface SimpleDropdownItem extends Omit<DropdownItemProps, 'content'> {
/** Content of the dropdown item. If the isDivider prop is true, this prop will be ignored. */
content?: React.ReactNode;
/** Unique identifier for the dropdown item, which is used in the dropdown onSelect callback */
Expand All @@ -24,9 +25,9 @@ export interface DropdownSimpleItem extends Omit<DropdownItemProps, 'content'> {
isDivider?: boolean;
}

export interface DropdownSimpleProps extends OUIAProps {
export interface SimpleDropdownProps extends Omit<DropdownProps, 'toggle'>, OUIAProps {
/** Initial items of the dropdown. */
initialItems?: DropdownSimpleItem[];
initialItems?: SimpleDropdownItem[];
/** @hide Forwarded ref */
innerRef?: React.Ref<any>;
/** Flag indicating the dropdown should be disabled. */
Expand All @@ -47,9 +48,13 @@ export interface DropdownSimpleProps extends OUIAProps {
toggleContent: React.ReactNode;
/** Variant style of the dropdown toggle. */
toggleVariant?: 'default' | 'plain' | 'plainText';
/** Width of the toggle. */
toggleWidth?: string;
/** Additional props passed to the toggle. */
toggleProps?: MenuToggleProps;
}

const DropdownSimpleBase: React.FunctionComponent<DropdownSimpleProps> = ({
const SimpleDropdownBase: React.FunctionComponent<SimpleDropdownProps> = ({
innerRef,
initialItems,
onSelect: onSelectProp,
Expand All @@ -59,13 +64,16 @@ const DropdownSimpleBase: React.FunctionComponent<DropdownSimpleProps> = ({
toggleContent,
isToggleFullWidth,
toggleVariant = 'default',
toggleWidth,
toggleProps,
shouldFocusToggleOnSelect,
...props
}: DropdownSimpleProps) => {
}: SimpleDropdownProps) => {
const [isOpen, setIsOpen] = React.useState(false);

const onSelect = (event: React.MouseEvent<Element, MouseEvent>, value: string | number) => {
onSelectProp && onSelectProp(event, value);
onToggleProp && onToggleProp(false);
setIsOpen(false);
};

Expand All @@ -83,6 +91,12 @@ const DropdownSimpleBase: React.FunctionComponent<DropdownSimpleProps> = ({
variant={toggleVariant}
aria-label={toggleAriaLabel}
isFullWidth={isToggleFullWidth}
style={
{
width: toggleWidth
} as React.CSSProperties
}
{...toggleProps}
>
{toggleContent}
</MenuToggle>
Expand All @@ -106,7 +120,10 @@ const DropdownSimpleBase: React.FunctionComponent<DropdownSimpleProps> = ({
isOpen={isOpen}
onSelect={onSelect}
shouldFocusToggleOnSelect={shouldFocusToggleOnSelect}
onOpenChange={(isOpen) => setIsOpen(isOpen)}
onOpenChange={(isOpen) => {
onToggleProp && onToggleProp(isOpen);
setIsOpen(isOpen);
}}
ref={innerRef}
{...props}
>
Expand All @@ -115,8 +132,8 @@ const DropdownSimpleBase: React.FunctionComponent<DropdownSimpleProps> = ({
);
};

export const DropdownSimple = React.forwardRef((props: DropdownSimpleProps, ref: React.Ref<any>) => (
<DropdownSimpleBase {...props} innerRef={ref} />
export const SimpleDropdown = React.forwardRef((props: SimpleDropdownProps, ref: React.Ref<any>) => (
<SimpleDropdownBase {...props} innerRef={ref} />
));

DropdownSimple.displayName = 'DropdownSimple';
SimpleDropdown.displayName = 'SimpleDropdown';
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DropdownSimple } from '../DropdownSimple';
import { SimpleDropdown } from '../SimpleDropdown';
import styles from '@patternfly/react-styles/css/components/MenuToggle/menu-toggle';

describe('Dropdown toggle', () => {
test('Renders dropdown toggle as not disabled when isDisabled is not true', () => {
render(<DropdownSimple toggleContent="Dropdown" />);
render(<SimpleDropdown toggleContent="Dropdown" />);

expect(screen.getByRole('button', { name: 'Dropdown' })).not.toBeDisabled();
});

test('Renders dropdown toggle as disabled when isDisabled is true', () => {
render(<DropdownSimple toggleContent="Dropdown" isDisabled />);
render(<SimpleDropdown toggleContent="Dropdown" isDisabled />);

expect(screen.getByRole('button', { name: 'Dropdown' })).toBeDisabled();
});

test('Passes toggleVariant', () => {
render(<DropdownSimple toggleContent="Dropdown" toggleVariant="plain" />);
render(<SimpleDropdown toggleContent="Dropdown" toggleVariant="plain" />);

expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveClass(styles.modifiers.plain);
});

test('Passes toggleWidth', () => {
render(<SimpleDropdown toggleContent="Dropdown" toggleWidth="500px" />);

expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveAttribute('style', 'width: 500px;');
});

test('Passes additional toggleProps', () => {
render(<SimpleDropdown toggleContent="Dropdown" toggleProps={{ id: 'toggle' }} />);

expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveAttribute('id', 'toggle');
});

test('Passes toggleAriaLabel', () => {
render(<DropdownSimple toggleContent="Dropdown" toggleAriaLabel="Aria label content" />);
render(<SimpleDropdown toggleContent="Dropdown" toggleAriaLabel="Aria label content" />);

expect(screen.getByRole('button')).toHaveAccessibleName('Aria label content');
});

test('Calls onToggle with next isOpen state when dropdown toggle is clicked', async () => {
const onToggle = jest.fn();
const user = userEvent.setup();
render(<DropdownSimple onToggle={onToggle} toggleContent="Dropdown" />);
render(<SimpleDropdown onToggle={onToggle} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -46,7 +58,7 @@ describe('Dropdown toggle', () => {
render(
<div>
<button>Actual</button>
<DropdownSimple initialItems={items} onToggle={onToggle} toggleContent="Dropdown" />
<SimpleDropdown initialItems={items} onToggle={onToggle} toggleContent="Dropdown" />
</div>
);

Expand All @@ -59,7 +71,7 @@ describe('Dropdown toggle', () => {
const onSelect = jest.fn();
const items = [{ content: 'Action', value: 1 }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" onSelect={onSelect} />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" onSelect={onSelect} />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -73,7 +85,7 @@ describe('Dropdown toggle', () => {
const onSelect = jest.fn();
const items = [{ content: 'Action', value: 1 }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" onSelect={onSelect} />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" onSelect={onSelect} />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -82,21 +94,21 @@ describe('Dropdown toggle', () => {
});

test('Does not pass isToggleFullWidth to menu toggle by default', () => {
render(<DropdownSimple toggleContent="Dropdown" />);
render(<SimpleDropdown toggleContent="Dropdown" />);

expect(screen.getByRole('button', { name: 'Dropdown' })).not.toHaveClass(styles.modifiers.fullWidth);
});

test('Passes isToggleFullWidth to menu toggle when passed in', () => {
render(<DropdownSimple isToggleFullWidth toggleContent="Dropdown" />);
render(<SimpleDropdown isToggleFullWidth toggleContent="Dropdown" />);

expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveClass(styles.modifiers.fullWidth);
});

test('Does not focus toggle on item select by default', async () => {
const items = [{ content: 'Action', value: 1 }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -109,7 +121,7 @@ describe('Dropdown toggle', () => {
test('Focuses toggle on item select when shouldFocusToggleOnSelect is true', async () => {
const items = [{ content: 'Action', value: 1 }];
const user = userEvent.setup();
render(<DropdownSimple shouldFocusToggleOnSelect initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown shouldFocusToggleOnSelect initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -120,7 +132,7 @@ describe('Dropdown toggle', () => {
});

test('Matches snapshot', () => {
const { asFragment } = render(<DropdownSimple toggleContent="Dropdown" />);
const { asFragment } = render(<SimpleDropdown toggleContent="Dropdown" />);

expect(asFragment()).toMatchSnapshot();
});
Expand All @@ -133,7 +145,7 @@ describe('Dropdown items', () => {
{ value: 'separator', isDivider: true }
];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -147,7 +159,7 @@ describe('Dropdown items', () => {
test('Renders with a link item', async () => {
const items = [{ content: 'Link', value: 1, to: '#' }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -159,7 +171,7 @@ describe('Dropdown items', () => {
test('Renders with items not disabled by default', async () => {
const items = [{ content: 'Action', value: 1 }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -171,7 +183,7 @@ describe('Dropdown items', () => {
test('Renders with a disabled item', async () => {
const items = [{ content: 'Action', value: 1, isDisabled: true }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -183,7 +195,7 @@ describe('Dropdown items', () => {
test('Spreads props on item', async () => {
const items = [{ content: 'Action', value: 1, id: 'Test' }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -196,7 +208,7 @@ describe('Dropdown items', () => {
const onClick = jest.fn();
const items = [{ content: 'Action', value: 1, onClick }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -213,7 +225,7 @@ describe('Dropdown items', () => {
{ content: 'Action 2', value: 2 }
];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -227,7 +239,7 @@ describe('Dropdown items', () => {
const onClick = jest.fn();
const items = [{ content: 'Action', value: 1, onClick, isDisabled: true }];
const user = userEvent.setup();
render(<DropdownSimple initialItems={items} toggleContent="Dropdown" />);
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand All @@ -244,7 +256,7 @@ describe('Dropdown items', () => {
{ content: 'Link', value: 'separator', to: '#', ouiaId: '3' }
];
const user = userEvent.setup();
const { asFragment } = render(<DropdownSimple ouiaId={4} initialItems={items} toggleContent="Dropdown" />);
const { asFragment } = render(<SimpleDropdown ouiaId={4} initialItems={items} toggleContent="Dropdown" />);

const toggle = screen.getByRole('button', { name: 'Dropdown' });
await user.click(toggle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports[`Dropdown items Matches snapshot 1`] = `
<button
aria-expanded="true"
class="pf-v5-c-menu-toggle pf-m-expanded"
data-ouia-component-id="OUIA-Generated-MenuToggle-default-21"
data-ouia-component-id="OUIA-Generated-MenuToggle-default-23"
data-ouia-component-type="PF5/MenuToggle"
data-ouia-safe="true"
type="button"
Expand Down Expand Up @@ -117,7 +117,7 @@ exports[`Dropdown toggle Matches snapshot 1`] = `
<button
aria-expanded="false"
class="pf-v5-c-menu-toggle"
data-ouia-component-id="OUIA-Generated-MenuToggle-default-12"
data-ouia-component-id="OUIA-Generated-MenuToggle-default-14"
data-ouia-component-type="PF5/MenuToggle"
data-ouia-safe="true"
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,21 @@ section: components
subsection: menus
template: true
beta: true
propComponents: ['DropdownSimple', 'DropdownSimpleItem']
propComponents: ['SimpleDropdown', 'SimpleDropdownItem']
---

Note: Templates live in their own package at [@patternfly/react-templates](https://www.npmjs.com/package/@patternfly/react-templates)!

For custom use cases, please see the dropdown component suite from [@patternfly/react-core](https://www.npmjs.com/package/@patternfly/react-core).

import {
Checkbox,
Divider,
Dropdown,
DropdownItem,
DropdownList,
DropdownItemProps,
Flex,
FlexItem,
MenuToggle,
MenuToggleElement,
OUIAProps
} from '@patternfly/react-core';
import { DropdownSimple, DropdownSimpleItem } from '@patternfly/react-templates';
import { Checkbox, Flex, FlexItem } from '@patternfly/react-core';
import { SimpleDropdown } from '@patternfly/react-templates';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';

## Examples

### Simple

```ts file="./DropdownSimpleExample.tsx"
```ts file="./SimpleDropdownExample.tsx"

```
Loading
Loading