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

Commit

Permalink
feat(hooks): add useMenu (#3197)
Browse files Browse the repository at this point in the history
* feat(hooks): add useMenu

* fix: not optional

* increase bundlesize with healthy margin
  • Loading branch information
Haroenv committed Nov 24, 2021
1 parent c772401 commit 15d1cc9
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 6 deletions.
4 changes: 4 additions & 0 deletions examples/hooks/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Pagination,
Panel,
RefinementList,
Menu,
SearchBox,
SortBy,
} from './components';
Expand Down Expand Up @@ -60,6 +61,9 @@ export function App() {
showMore={true}
/>
</Panel>
<Panel header="Categories">
<Menu attribute="categories" showMore={true} />
</Panel>
<Panel header="Hierarchy">
<HierarchicalMenu
attributes={[
Expand Down
62 changes: 62 additions & 0 deletions examples/hooks/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import { useMenu, UseMenuProps } from 'react-instantsearch-hooks';

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

export type MenuProps = React.ComponentProps<'div'> & UseMenuProps;

export function Menu(props: MenuProps) {
const {
canToggleShowMore,
isShowingMore,
items,
refine,
createURL,
toggleShowMore,
} = useMenu(props);

return (
<div className={cx('ais-Menu', props.className)}>
<ul className="ais-Menu-list">
{items.map((item) => (
<li
key={item.value}
className={cx(
'ais-Menu-item',
item.isRefined && 'ais-Menu-item--selected'
)}
>
<a
className="ais-Menu-link"
onClick={(event) => {
if (isSpecialClick(event)) {
return;
}
event.preventDefault();
refine(item.value);
}}
href={createURL(item.value)}
>
<span className="ais-Menu-label">{item.label}</span>
<span className="ais-Menu-count">{item.count}</span>
</a>
</li>
))}
</ul>

{props.showMore && (
<button
className={cx(
'ais-Menu-showMore',
!canToggleShowMore && 'ais-Menu-showMore--disabled'
)}
disabled={!canToggleShowMore}
onClick={toggleShowMore}
>
{isShowingMore ? 'Show less' : 'Show more'}
</button>
)}
</div>
);
}
1 change: 1 addition & 0 deletions examples/hooks/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './Configure';
export * from './Hits';
export * from './HierarchicalMenu';
export * from './Menu';
export * from './Pagination';
export * from './RefinementList';
export * from './SearchBox';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
},
{
"path": "packages/react-instantsearch-hooks/dist/umd/ReactInstantSearchHooks.min.js",
"maxSize": "28.75 kB"
"maxSize": "32 kB"
},
{
"path": "packages/react-instantsearch-dom/dist/umd/ReactInstantSearchDOM.min.js",
Expand Down
136 changes: 131 additions & 5 deletions packages/react-instantsearch-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ const searchClient = algoliasearch(
</InstantSearch>;
```

### `initialUiState`
#### `initialUiState`

> `object`
Expand All @@ -245,7 +245,7 @@ Provides an initial state to your React InstantSearch widgets using InstantSearc
</InstantSearch>
```

### `onStateChange`
#### `onStateChange`

> `function`
Expand All @@ -268,7 +268,7 @@ This is useful to perform custom logic whenever the state changes.
</InstantSearch>
```

### `stalledSearchDelay`
#### `stalledSearchDelay`

> `number` | defaults to `200`
Expand All @@ -283,7 +283,7 @@ Defines a time period after which a search is considered stalled. You can find m
</InstantSearch>
```

### `routing`
#### `routing`

> `boolean | object`
Expand All @@ -298,7 +298,7 @@ The router configuration used to save the UI state into the URL, or any client-s
</InstantSearch>
```

### `suppressExperimentalWarning`
#### `suppressExperimentalWarning`

> `boolean`
Expand Down Expand Up @@ -468,6 +468,132 @@ function Hits(props) {
}
```

### `useMenu`

> `(props: UseMenuProps) => MenuRenderState`
Hook to use a [menu](https://www.algolia.com/doc/api-reference/widgets/menu/js/).

**Types**

<details>
<summary><code>UseMenuProps</code></summary>

```ts
type UseMenuProps = {
/**
* The name of the attribute in the records.
*/
attribute: string;
/**
* The max number of items to display when
* `showMoreLimit` is not set or if the widget is showing less value.
*/
limit?: number;
/**
* Whether to display a button that expands the number of items.
*/
showMore?: boolean;
/**
* The max number of items to display if the widget
* is showing more items.
*/
showMoreLimit?: number;
/**
* How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.
*
* You can also use a sort function that behaves like the standard Javascript [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax).
*
* If a facetOrdering is set in the index settings, it is used when sortBy isn't passed
*/
sortBy?: SortBy<MenuItem>;
/**
* Function to transform the items passed to the templates.
*/
transformItems?: TransformItems<MenuItem>;
};
```

</details>

<details>
<summary><code>MenuRenderState</code></summary>

```ts
export type MenuItem = {
/**
* The value of the refinement list item.
*/
value: string;
/**
* Human-readable value of the refinement list item.
*/
label: string;
/**
* Number of matched results after refinement is applied.
*/
count: number;
/**
* Indicates if the list item is refined.
*/
isRefined: boolean;
};

type MenuRenderState = {
/**
* The list of filtering values returned from Algolia API.
*/
items: MenuItem[];
/**
* Creates the next state url for a selected refinement.
*/
createURL: (value: string) => string;
/**
* Action to apply selected refinements.
*/
refine(value: string): void;
/**
* Send event to insights middleware
*/
sendEvent: (
eventType: string,
facetValue: string,
eventName?: string
) => void;
/**
* `true` if a refinement can be applied.
*/
canRefine: boolean;
/**
* `true` if the toggleShowMore button can be activated (enough items to display more or
* already displaying more than `limit` items)
*/
canToggleShowMore: boolean;
/**
* True if the menu is displaying all the menu items.
*/
isShowingMore: boolean;
/**
* Toggles the number of values displayed between `limit` and `showMoreLimit`.
*/
toggleShowMore: () => void;
};
```

</details>

**Example**

```jsx
function Menu(props) {
const { items } = useMenu(props);

return {
/* Markup */
};
}
```

### `useHierarchicalMenu`

> `(props: UseHierarchicalMenuProps) => HierarchicalMenuRenderState`
Expand Down
42 changes: 42 additions & 0 deletions packages/react-instantsearch-hooks/src/__tests__/useMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { renderHook } from '@testing-library/react-hooks';

import { createInstantSearchTestWrapper } from '../../../../test/utils';
import { useMenu } from '../useMenu';

describe('useMenu', () => {
test('returns the connector render state', async () => {
const wrapper = createInstantSearchTestWrapper();
const { result, waitForNextUpdate } = renderHook(
() => useMenu({ attribute: 'attribute' }),
{
wrapper,
}
);

// Initial render state from manual `getWidgetRenderState`
expect(result.current).toEqual({
canRefine: false,
canToggleShowMore: false,
createURL: expect.any(Function),
isShowingMore: false,
items: [],
refine: expect.any(Function),
sendEvent: expect.any(Function),
toggleShowMore: expect.any(Function),
});

await waitForNextUpdate();

// InstantSearch.js state from the `render` lifecycle step
expect(result.current).toEqual({
canRefine: false,
canToggleShowMore: false,
createURL: expect.any(Function),
isShowingMore: false,
items: [],
refine: expect.any(Function),
sendEvent: expect.any(Function),
toggleShowMore: expect.any(Function),
});
});
});
1 change: 1 addition & 0 deletions packages/react-instantsearch-hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './useConfigure';
export * from './useConnector';
export * from './useHits';
export * from './useHierarchicalMenu';
export * from './useMenu';
export * from './usePagination';
export * from './useRefinementList';
export * from './useSearchBox';
Expand Down
17 changes: 17 additions & 0 deletions packages/react-instantsearch-hooks/src/useMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import connectMenu from 'instantsearch.js/es/connectors/menu/connectMenu';

import { useConnector } from './useConnector';

import type {
MenuConnectorParams,
MenuWidgetDescription,
} from 'instantsearch.js/es/connectors/menu/connectMenu';

export type UseMenuProps = MenuConnectorParams;

export function useMenu(props: UseMenuProps) {
return useConnector<MenuConnectorParams, MenuWidgetDescription>(
connectMenu,
props
);
}

0 comments on commit 15d1cc9

Please sign in to comment.