Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

feat(hooks): implement SortBy component #3373

Merged
merged 12 commits into from
Mar 4, 2022
2 changes: 1 addition & 1 deletion examples/hooks/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Hits,
Pagination,
SearchBox,
SortBy,
} from 'react-instantsearch-hooks-dom';

import {
Expand All @@ -26,7 +27,6 @@ import {
QueryRuleCustomData,
RangeInput,
RefinementList,
SortBy,
ToggleRefinement,
} from './components';
import { Tab, Tabs } from './components/layout';
Expand Down
1 change: 0 additions & 1 deletion examples/hooks/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ export * from './QueryRuleContext';
export * from './QueryRuleCustomData';
export * from './RangeInput';
export * from './RefinementList';
export * from './SortBy';
export * from './ToggleRefinement';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Make certain keys of an object optional.
*/
export type PartialKeys<TObj, TKeys extends keyof TObj> = Omit<TObj, TKeys> &
Partial<Pick<TObj, TKeys>>;
1 change: 1 addition & 0 deletions packages/react-instantsearch-hooks-dom/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PartialKeys';
41 changes: 41 additions & 0 deletions packages/react-instantsearch-hooks-dom/src/ui/SortBy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';

import { cx } from './lib/cx';

import type { UseSortByProps } from 'react-instantsearch-hooks';

export type SortByProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
'onChange'
> &
Pick<UseSortByProps, 'items'> &
Pick<React.SelectHTMLAttributes<HTMLSelectElement>, 'value'> & {
onChange?(value: string): void;
};

export function SortBy({
items,
value,
onChange = () => {},
...props
}: SortByProps) {
return (
<div {...props} className={cx('ais-SortBy', props.className)}>
<select
className="ais-SortBy-select"
onChange={(event) => onChange(event.target.value)}
value={value}
>
{items.map((item) => (
<option
className="ais-SortBy-option"
key={item.value}
value={item.value}
>
{item.label}
</option>
))}
</select>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

import { SortBy } from '../SortBy';

describe('SortBy', () => {
test('renders with items', () => {
const { container } = render(
<SortBy
items={[
{ label: 'Featured', value: 'instant_search' },
{ label: 'Price (asc)', value: 'instant_search_price_asc' },
{ label: 'Price (desc)', value: 'instant_search_price_desc' },
]}
/>
);

expect(document.querySelector('.ais-SortBy-select')).toHaveValue(
'instant_search'
);
expect(container).toMatchInlineSnapshot(`
<div>
<div
class="ais-SortBy"
>
<select
class="ais-SortBy-select"
>
<option
class="ais-SortBy-option"
value="instant_search"
>
Featured
</option>
<option
class="ais-SortBy-option"
value="instant_search_price_asc"
>
Price (asc)
</option>
<option
class="ais-SortBy-option"
value="instant_search_price_desc"
>
Price (desc)
</option>
</select>
</div>
</div>
`);
});

test('sets the value', () => {
render(
<SortBy
value="instant_search_price_asc"
items={[
{ label: 'Featured', value: 'instant_search' },
{ label: 'Price (asc)', value: 'instant_search_price_asc' },
{ label: 'Price (desc)', value: 'instant_search_price_desc' },
]}
/>
);

expect(document.querySelector('.ais-SortBy-select')).toHaveValue(
'instant_search_price_asc'
);
});

test('calls an `onChange` callback when selecting an option', () => {
const onChange = jest.fn();
const { getByRole } = render(
<SortBy
onChange={onChange}
items={[
{ label: 'Featured', value: 'instant_search' },
{ label: 'Price (asc)', value: 'instant_search_price_asc' },
{ label: 'Price (desc)', value: 'instant_search_price_desc' },
]}
/>
);

userEvent.selectOptions(
document.querySelector('.ais-SortBy-select') as HTMLSelectElement,
getByRole('option', { name: 'Price (asc)' })
);

expect(document.querySelector('.ais-SortBy-select')).toHaveValue(
'instant_search_price_asc'
);
expect(onChange).toHaveBeenCalledTimes(1);
});

test('forwards a custom class name to the root element', () => {
const { container } = render(
<SortBy
className="MySortBy"
items={[
{ label: 'Featured', value: 'instant_search' },
{ label: 'Price (asc)', value: 'instant_search_price_asc' },
{ label: 'Price (desc)', value: 'instant_search_price_desc' },
]}
/>
);

expect(document.querySelector('.ais-SortBy')).toHaveClass('MySortBy');
expect(container).toMatchInlineSnapshot(`
<div>
<div
class="ais-SortBy MySortBy"
>
<select
class="ais-SortBy-select"
>
<option
class="ais-SortBy-option"
value="instant_search"
>
Featured
</option>
<option
class="ais-SortBy-option"
value="instant_search_price_asc"
>
Price (asc)
</option>
<option
class="ais-SortBy-option"
value="instant_search_price_desc"
>
Price (desc)
</option>
</select>
</div>
</div>
`);
});

test('forwards `div` props to the root element', () => {
const { container } = render(
<SortBy
title="Some custom title"
items={[
{ label: 'Featured', value: 'instant_search' },
{ label: 'Price (asc)', value: 'instant_search_price_asc' },
{ label: 'Price (desc)', value: 'instant_search_price_desc' },
]}
/>
);

expect(document.querySelector('.ais-SortBy')).toHaveAttribute(
'title',
'Some custom title'
);
expect(container).toMatchInlineSnapshot(`
<div>
<div
class="ais-SortBy"
title="Some custom title"
>
<select
class="ais-SortBy-select"
>
<option
class="ais-SortBy-option"
value="instant_search"
>
Featured
</option>
<option
class="ais-SortBy-option"
value="instant_search_price_asc"
>
Price (asc)
</option>
<option
class="ais-SortBy-option"
value="instant_search_price_desc"
>
Price (desc)
</option>
</select>
</div>
</div>
`);
});
});
34 changes: 34 additions & 0 deletions packages/react-instantsearch-hooks-dom/src/widgets/SortBy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { useSortBy } from 'react-instantsearch-hooks';

import { SortBy as SortByUiComponent } from '../ui/SortBy';

import type { SortByProps as SortByUiComponentProps } from '../ui/SortBy';
import type { UseSortByProps } from 'react-instantsearch-hooks';

export type SortByProps = Omit<
SortByUiComponentProps,
'items' | 'value' | 'onSelect'
> &
UseSortByProps;

export function SortBy({ items, transformItems, ...props }: SortByProps) {
const { currentRefinement, options, refine } = useSortBy(
{
items,
transformItems,
},
{
$$widgetType: 'ais.sortBy',
}
);

return (
<SortByUiComponent
{...props}
value={currentRefinement}
items={options}
onChange={refine}
/>
);
}
Loading