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

Filters - Filter Menu - menu header #2951

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/dirty-coins-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools-uikit/filters': minor
---

Implement FilterMenu header component.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a changeset here, since we aren't publishing filters for public consumption until the entire feature is done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated here: 270993d
Thank you

18 changes: 17 additions & 1 deletion packages/components/filters/src/filter-menu/filter-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,23 @@ export type TFilterMenuProps = {
function FilterMenu(props: TFilterMenuProps) {
return (
<DropdownMenu triggerElement={<TriggerButton label={props.label} />}>
<Header />
<Header
// For storybook purposes, we are not using the actual props - will be taken off eventually.
label={props.label}
operandOptions={[
{ value: 'is', label: 'is' },
{ value: 'is not', label: 'is NOT' },
]}
renderOperandsInput={true}
onSelectOperand={(value) => value}
onSort={() => {}}
menuHeaderWidth={'100px'}
/>
<p>text</p>
<p>text</p>
<p>text</p>
<p>text</p>
<p>text</p>
<Footer />
</DropdownMenu>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,78 @@
import { screen, render } from '../../../../../../test/test-utils';
import { render, screen, fireEvent } from '../../../../../../test/test-utils';
import Header from './header';

/**
* THIS IS A PLACEHOLDER, PLEASE UPDATE IT
*/
const operandOptions = [
{ value: 'is', label: 'IS' },
{ value: 'isNot', label: 'IS NOT' },
];

const FilterSetup = () => (
<Header
label="Filter Label"
operandOptions={operandOptions}
onSelectOperand={jest.fn()}
onSort={jest.fn()}
/>
);

describe('FilterMenu Header', () => {
it('should render the header', async () => {
await render(<Header />);
await screen.findByText('header');
it('should render the header label', async () => {
render(<FilterSetup />);

expect(screen.getByText('Filter Label')).toBeInTheDocument();
});

it('should conditionally render the SelectInput when operandOptions are provided', async () => {
render(<FilterSetup />);

expect(screen.getByRole('combobox')).toBeInTheDocument();
});

it('should not render SelectInput when operandOptions are not provided', () => {
render(
<Header
label="Filter Label"
onSelectOperand={jest.fn()}
onSort={jest.fn()}
/>
);

expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
});

it('should conditionally render the IconButton when onSort is provided', async () => {
render(<FilterSetup />);

expect(screen.getByLabelText('Sort')).toBeInTheDocument();
});

it('should not render IconButton when onSort is not provided', () => {
render(
<Header
label="Filter Label"
onSelectOperand={jest.fn()}
operandOptions={operandOptions}
/>
);

expect(screen.queryByLabelText('Sort')).not.toBeInTheDocument();
});

it('should toggle isActive and call onSort when IconButton is clicked', async () => {
const onSortMock = jest.fn();
render(
<Header
label="Filter Label"
operandOptions={operandOptions}
onSelectOperand={jest.fn()}
onSort={onSortMock}
/>
);

const sortButton = screen.getByLabelText('Sort');

fireEvent.click(sortButton);

expect(onSortMock).toHaveBeenCalled();
});
});
82 changes: 79 additions & 3 deletions packages/components/filters/src/filter-menu/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,81 @@
function Header() {
return <div>header</div>;
}
import SelectInput from '@commercetools-uikit/select-input';
import IconButton from '@commercetools-uikit/icon-button';
import Spacings from '@commercetools-uikit/spacings';
import { SortingIcon } from '@commercetools-uikit/icons';
import { designTokens } from '@commercetools-uikit/design-system';
import { css } from '@emotion/react';
import { useState } from 'react';

type TOption = {
value: string;
label: string;
};

type THeaderProps = {
label: string;
operandOptions?: Array<TOption>;
renderOperandsInput?: boolean;
onSelectOperand: (value: string) => void;
onSort?: () => void;
menuHeaderWidth?: string;
};

const headerContainerStyles = css`
display: flex;
align-items: center;
padding-bottom: ${designTokens.spacing10};
border-bottom: 1px solid ${designTokens.colorNeutral90};
`;

const getSelectInputStyles = (props: THeaderProps) => css`
width: ${props.menuHeaderWidth};
margin: ${designTokens.spacing20};
`;

const Header = (props: THeaderProps) => {
const [headerSelectOptions, setHeaderSelectOptions] = useState<string>();
const [isActive, setIsActive] = useState(false);

return (
<Spacings.Inline>
<div css={headerContainerStyles}>
<div>{props.label}</div>
{props.operandOptions && (
<div css={getSelectInputStyles(props)}>
<SelectInput
appearance="quiet"
value={
headerSelectOptions
? headerSelectOptions
: props.operandOptions[0].value
}
isCondensed={true}
isSearchable={false}
options={props.operandOptions}
onChange={(event) => {
setHeaderSelectOptions(event.target.value as string);
props.onSelectOperand(event.target.value as string);
}}
/>
</div>
)}

{props.onSort && (
<IconButton
size="20"
theme={isActive ? 'info' : 'default'}
label="Sort"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might notice a slight change between the IconButton when Active. This is because "round" prop is deprecated and I have checked in with Filip to confirm we are using the new prop design(square).

icon={<SortingIcon />}
isToggleButton={true}
onClick={() => {
setIsActive(!isActive);
return props.onSort && props.onSort();
}}
/>
)}
</div>
</Spacings.Inline>
);
};

export default Header;
4 changes: 3 additions & 1 deletion packages/components/filters/src/filters.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Meta, StoryFn } from '@storybook/react';
import Filters from './filters';
import FilterMenu from './filter-menu';

const meta: Meta<typeof Filters> = {
title: 'components/Filters',
Expand All @@ -16,5 +17,6 @@ export default meta;
type Story = StoryFn<typeof Filters>;

export const BasicExample: Story = () => {
return <Filters label={'test'} />;
// return <Filters label={'test'} />;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only for testing purposes, I will return back to its state before merging

return <FilterMenu label={'Size'} />;
};
Loading