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

Create Facets component #360

Merged
merged 2 commits into from
May 1, 2023
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
30 changes: 30 additions & 0 deletions docs/search-ui-react.facets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [Facets](./search-ui-react.facets.md)

## Facets() function

A component that displays all facets applicable to the current vertical search.

<b>Signature:</b>

```typescript
export declare function Facets(props: FacetsProps): JSX.Element;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| props | [FacetsProps](./search-ui-react.facetsprops.md) | [FacetsProps](./search-ui-react.facetsprops.md) |

<b>Returns:</b>

JSX.Element

A React component for facets

## Remarks

This component is a quick way of getting facets on the page, and it will render standard facets, numerical facets, and hierarchical facets. The [StandardFacets()](./search-ui-react.standardfacets.md)<!-- -->, [NumericalFacets()](./search-ui-react.numericalfacets.md)<!-- -->, and [HierarchicalFacets()](./search-ui-react.hierarchicalfacets.md) components can be used instead for more control over facet configuration.

11 changes: 11 additions & 0 deletions docs/search-ui-react.facetscssclasses.facetscontainer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [FacetsCssClasses](./search-ui-react.facetscssclasses.md) &gt; [facetsContainer](./search-ui-react.facetscssclasses.facetscontainer.md)

## FacetsCssClasses.facetsContainer property

<b>Signature:</b>

```typescript
facetsContainer?: string;
```
20 changes: 20 additions & 0 deletions docs/search-ui-react.facetscssclasses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [FacetsCssClasses](./search-ui-react.facetscssclasses.md)

## FacetsCssClasses interface

The CSS class interface for [Facets()](./search-ui-react.facets.md)<!-- -->.

<b>Signature:</b>

```typescript
export interface FacetsCssClasses
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [facetsContainer?](./search-ui-react.facetscssclasses.facetscontainer.md) | string | <i>(Optional)</i> |

13 changes: 13 additions & 0 deletions docs/search-ui-react.facetsprops.customcssclasses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [FacetsProps](./search-ui-react.facetsprops.md) &gt; [customCssClasses](./search-ui-react.facetsprops.customcssclasses.md)

## FacetsProps.customCssClasses property

CSS classes for customizing the component styling.

<b>Signature:</b>

```typescript
customCssClasses?: FacetsCssClasses;
```
20 changes: 20 additions & 0 deletions docs/search-ui-react.facetsprops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [FacetsProps](./search-ui-react.facetsprops.md)

## FacetsProps interface

Props for the [Facets()](./search-ui-react.facets.md) component.

<b>Signature:</b>

```typescript
export interface FacetsProps
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [customCssClasses?](./search-ui-react.facetsprops.customcssclasses.md) | [FacetsCssClasses](./search-ui-react.facetscssclasses.md) | <i>(Optional)</i> CSS classes for customizing the component styling. |

3 changes: 3 additions & 0 deletions docs/search-ui-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
| [DropdownItem(\_props)](./search-ui-react.dropdownitem.md) | A wrapper component for specifying a DropdownItemWithIndex. The index will be automatically provided by the Dropdown component instance. |
| [executeAutocomplete(searchActions)](./search-ui-react.executeautocomplete.md) | Executes a universal/vertical autocomplete search and return the corresponding response. |
| [executeSearch(searchActions)](./search-ui-react.executesearch.md) | Executes a universal/vertical search. |
| [Facets(props)](./search-ui-react.facets.md) | A component that displays all facets applicable to the current vertical search. |
| [FilterDivider({ className })](./search-ui-react.filterdivider.md) | A divider component used to separate NumericalFacets, HierarchicalFacets, StandardFacets, and StaticFilters. |
| [FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, sectioned, customCssClasses })](./search-ui-react.filtersearch.md) | A component which allows a user to search for filters associated with specific entities and fields. |
| [Geolocation\_2({ geolocationOptions, radius, label, GeolocationIcon, handleClick, customCssClasses, })](./search-ui-react.geolocation_2.md) | A React Component which collects location information to create a location filter and perform a new search. |
Expand Down Expand Up @@ -60,6 +61,8 @@
| [CtaData](./search-ui-react.ctadata.md) | The shape of a StandardCard CTA field's data. |
| [DirectAnswerCssClasses](./search-ui-react.directanswercssclasses.md) | The CSS class interface for [DirectAnswer()](./search-ui-react.directanswer.md)<!-- -->. |
| [DirectAnswerProps](./search-ui-react.directanswerprops.md) | Props for [DirectAnswer()](./search-ui-react.directanswer.md)<!-- -->. |
| [FacetsCssClasses](./search-ui-react.facetscssclasses.md) | The CSS class interface for [Facets()](./search-ui-react.facets.md)<!-- -->. |
| [FacetsProps](./search-ui-react.facetsprops.md) | Props for the [Facets()](./search-ui-react.facets.md) component. |
| [FilterGroupCssClasses](./search-ui-react.filtergroupcssclasses.md) | The CSS class interface for FilterGroup. |
| [FilterGroupProps](./search-ui-react.filtergroupprops.md) | Props for the FilterGroup component. |
| [FilterOptionConfig](./search-ui-react.filteroptionconfig.md) | The configuration data for a field value filter option. |
Expand Down
14 changes: 14 additions & 0 deletions etc/search-ui-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,20 @@ export function executeAutocomplete(searchActions: SearchActions): Promise<Autoc
// @public
export function executeSearch(searchActions: SearchActions): Promise<void>;

// @public
export function Facets(props: FacetsProps): JSX.Element;

// @public
export interface FacetsCssClasses {
// (undocumented)
facetsContainer?: string;
}

// @public
export interface FacetsProps {
customCssClasses?: FacetsCssClasses;
}

// @public
export type FeedbackType = 'THUMBS_UP' | 'THUMBS_DOWN';

Expand Down
45 changes: 45 additions & 0 deletions src/components/Facets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { StandardFacets } from './StandardFacets';
import { NumericalFacets } from './NumericalFacets';

/**
* The CSS class interface for {@link Facets}.
*
* @public
*/
export interface FacetsCssClasses {
facetsContainer?: string
}

/**
* Props for the {@link Facets} component.
*
* @public
*/
export interface FacetsProps {
/** CSS classes for customizing the component styling. */
customCssClasses?: FacetsCssClasses
}

/**
* A component that displays all facets applicable to the current vertical search.
*
* @remarks
* This component is a quick way of getting facets on the page, and it will render standard facets,
* numerical facets, and hierarchical facets. The {@link StandardFacets}, {@link NumericalFacets},
Copy link
Member

Choose a reason for hiding this comment

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

it won't currently use hierarchical facets right?

Copy link
Member Author

Choose a reason for hiding this comment

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

It will render them but as standard facets

* and {@link HierarchicalFacets} components can be used instead for more control over facet
* configuration.
*
* @param props - {@link FacetsProps}
* @returns A React component for facets
*
* @public
*/
export function Facets(props: FacetsProps) {
const { customCssClasses = {} } = props;
return (
<div className={customCssClasses.facetsContainer}>
<StandardFacets/>
<NumericalFacets />
</div>
);
}
6 changes: 6 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ export {
NumericalFacetsProps
} from './NumericalFacets';

export {
Facets,
FacetsCssClasses,
FacetsProps
} from './Facets';

export {
FilterGroupProps,
FilterGroupCssClasses
Expand Down
2 changes: 2 additions & 0 deletions test-site/src/pages/PeoplePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ export function PeoplePage() {
title='Static Employee Department'
filterOptions={employeeFilterConfigs}
/>
<FilterDivider />
<StaticFilters
fieldId='c_hierarchicalFacet'
title='Static Hierarchical Facets'
filterOptions={hierarchicalFilterConfigs}
/>
<FilterDivider />
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this only wrapping StaticFilters and br tag is used on line 73?

Copy link
Member Author

Choose a reason for hiding this comment

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

These aren't wrapping StaticFilters and are instead standalone filter dividers. The facets dynamically include dividers where needed, but static filters do not

<NumericalFacets />
<StandardFacets
excludedFieldIds={hierarchicalFacetFieldIds}
Expand Down
5 changes: 5 additions & 0 deletions tests/__utils__/facets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DisplayableFacetOption } from '@yext/search-headless-react';

export function getOptionLabelText(option: DisplayableFacetOption) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a an example of what this is trying to match?

return `${option.displayName} (${option.count})`;
}
26 changes: 26 additions & 0 deletions tests/components/Facets.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ComponentMeta, Story } from '@storybook/react';
import { SearchHeadlessContext, State } from '@yext/search-headless-react';
import { generateMockedHeadless } from '../__fixtures__/search-headless';
import { RecursivePartial } from '../__utils__/mocks';
import { DisplayableFacets } from '../__fixtures__/data/filters';
import { Facets, FacetsProps } from '../../src';

const meta: ComponentMeta<typeof Facets> = {
title: 'Facets',
component: Facets
};
export default meta;

const mockedHeadlessState: RecursivePartial<State> = {
filters: {
facets: DisplayableFacets
}
};

export const Primary: Story<FacetsProps> = (args) => {
return (
<SearchHeadlessContext.Provider value={generateMockedHeadless(mockedHeadlessState)}>
<Facets {...args} />
</SearchHeadlessContext.Provider>
);
};
91 changes: 91 additions & 0 deletions tests/components/Facets.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { render, screen } from '@testing-library/react';
import {
Source,
State
} from '@yext/search-headless-react';
import { mockAnswersHooks, mockAnswersState } from '../__utils__/mocks';
import { DisplayableFacets } from '../__fixtures__/data/filters';
import { Facets } from '../../src';
import { getOptionLabelText } from '../__utils__/facets';

const mockedState: Partial<State> = {
filters: {
static: [],
facets: DisplayableFacets
},
vertical: {
verticalKey: 'vertical',
results: [{
source: Source.KnowledgeManager,
rawData: {}
}],
appliedQueryFilters: []
},
searchStatus: {
isLoading: false
},
meta: {
searchType: 'vertical'
}
};

const mockedActions = {
state: mockedState,
setOffset: jest.fn(),
setFacetOption: jest.fn(),
executeVerticalQuery: jest.fn()
};

const mockedUtils = {
isCloseMatch: () => true
};

jest.mock('@yext/search-headless-react');

describe('Facets', () => {
beforeEach(() => {
mockAnswersHooks({ mockedState, mockedActions, mockedUtils });
});
it('Properly renders standard facets if present', () => {
render(<Facets/>);
const regularFilter = DisplayableFacets[0];

expect(screen.getByText(regularFilter.displayName)).toBeDefined();
regularFilter.options.forEach(o => {
expect(screen.getByText(getOptionLabelText(o))).toBeDefined();
});
});

it('Properly renders numerical facets if present', () => {
render(<Facets/>);
const numericalFilter = DisplayableFacets[1];

expect(screen.getByText(numericalFilter.displayName)).toBeDefined();
numericalFilter.options.forEach(o => {
expect(screen.getByText(o.displayName)).toBeDefined();
});
});

it('Does not render filters if no facets are present', () => {
mockAnswersState({
...mockedState,
filters: {
static: [],
facets: []
}
});
render(<Facets/>);
const regularFilter = DisplayableFacets[0];
const numericalFilter = DisplayableFacets[1];

expect(screen.queryByText(numericalFilter.displayName)).toBeNull();
numericalFilter.options.forEach(o => {
expect(screen.queryByText(o.displayName)).toBeNull();
});

expect(screen.queryByText(regularFilter.displayName)).toBeNull();
regularFilter.options.forEach(o => {
expect(screen.queryByText(`${o.displayName} (${o.count})}`)).toBeNull();
});
});
});
9 changes: 3 additions & 6 deletions tests/components/StandardFacets.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { render, screen } from '@testing-library/react';
import { DisplayableFacetOption, FacetOption, Source, State, SearchActions } from '@yext/search-headless-react';
import { FacetOption, Source, State, SearchActions } from '@yext/search-headless-react';
import { mockAnswersHooks, spyOnActions } from '../__utils__/mocks';
import userEvent from '@testing-library/user-event';
import { DisplayableFacets } from '../__fixtures__/data/filters';
import { StandardFacets } from '../../src/components';
import { getOptionLabelText } from '../__utils__/facets';

const mockedState: Partial<State> = {
filters: {
Expand Down Expand Up @@ -157,8 +158,4 @@ function expectFacetOptionSet(
{ matcher: option.matcher, value: option.value },
selected
);
}

function getOptionLabelText(option: DisplayableFacetOption) {
return `${option.displayName} (${option.count})`;
}
}