Skip to content

Commit

Permalink
feat(templates): toggle props & improvements (#10473)
Browse files Browse the repository at this point in the history
* feat(templates): toggle props & improvements

* remove toggleContent from typeahead template

* update template names

* update tests

* added SimpleSelect tests

* fix yarnlock
  • Loading branch information
kmcfaul authored Jun 20, 2024
1 parent 53ca336 commit 315fc80
Show file tree
Hide file tree
Showing 20 changed files with 657 additions and 11,427 deletions.
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

0 comments on commit 315fc80

Please sign in to comment.