Skip to content

Commit

Permalink
UIEH-1440: Package Record - Title List Accordion - Improve Visibility…
Browse files Browse the repository at this point in the history
… of Search Within/Filter/Sort options. (#1757)
  • Loading branch information
Dmytro-Melnyshyn authored Jan 14, 2025
1 parent fc842b3 commit 68a7c1c
Show file tree
Hide file tree
Showing 30 changed files with 1,148 additions and 169 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Add `target="_blank"` to the `New` button of agreements accordion. (UIEH-1446)
* Remove `shouldFocus` HOC and all related code. (UIEH-1444)
* Package Record - Title List Accordion - Improve Visibility of Search Within/Filter/Sort options. (UIEH-1440)

## [10.0.1] (https://github.com/folio-org/ui-eholdings/tree/v10.0.1) (2024-11-12)

Expand Down
4 changes: 4 additions & 0 deletions src/components/access-type-filter/access-type-filter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.headline {
display: flex;
align-items: baseline;
}
98 changes: 98 additions & 0 deletions src/components/access-type-filter/access-type-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useRef } from 'react';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';

import { Icon } from '@folio/stripes/components';
import { MultiSelectionFilter } from '@folio/stripes/smart-components';

import { SearchByCheckbox } from '../search-by-checkbox';
import { ClearButton } from '../clear-button';
import { accessTypesReduxStateShape } from '../../constants';

import styles from './access-type-filter.css';

const propTypes = {
accessTypesStoreData: accessTypesReduxStateShape.isRequired,
dataOptions: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string,
})).isRequired,
handleStandaloneFilterChange: PropTypes.func.isRequired,
onStandaloneFilterChange: PropTypes.func.isRequired,
onStandaloneFilterToggle: PropTypes.func.isRequired,
searchByAccessTypesEnabled: PropTypes.bool.isRequired,
selectedValues: PropTypes.array.isRequired,
showClearButton: PropTypes.bool,
};

const AccessTypesFilter = ({
accessTypesStoreData,
searchByAccessTypesEnabled,
selectedValues,
onStandaloneFilterChange,
onStandaloneFilterToggle,
dataOptions,
handleStandaloneFilterChange,
showClearButton = false,
}) => {
const intl = useIntl();
const labelRef = useRef(null);

const accessStatusTypesExist = !!accessTypesStoreData?.items?.data?.length;
const accessTypeFilterLabel = intl.formatMessage({ id: 'ui-eholdings.accessTypes.filter' });

const handleClearButtonClick = () => {
onStandaloneFilterChange({ 'access-type': undefined });
labelRef.current?.focus();
};

if (accessTypesStoreData?.isLoading) {
return <Icon icon="spinner-ellipsis" />;
}

if (!accessStatusTypesExist) {
return null;
}

return (
<>
<div className={styles.headline}>
<span
className="sr-only"
id="accessTypesFilter-label"
>
{intl.formatMessage({ id: 'ui-eholdings.settings.accessStatusTypes' })}
</span>
<SearchByCheckbox
ref={labelRef}
filterType="access-type"
isEnabled={searchByAccessTypesEnabled}
onStandaloneFilterToggle={onStandaloneFilterToggle}
/>
{showClearButton && (
<ClearButton
show={selectedValues.length > 0}
label={accessTypeFilterLabel}
onClick={handleClearButtonClick}
/>
)}
</div>
<div data-testid="search-form-access-type-filter">
<MultiSelectionFilter
id="accessTypeFilterSelect"
ariaLabel={accessTypeFilterLabel}
dataOptions={dataOptions}
name="access-type"
onChange={handleStandaloneFilterChange}
selectedValues={selectedValues}
disabled={!searchByAccessTypesEnabled}
aria-labelledby="accessTypesFilter-label"
/>
</div>
</>
);
};

AccessTypesFilter.propTypes = propTypes;

export { AccessTypesFilter };
100 changes: 100 additions & 0 deletions src/components/access-type-filter/access-type-filter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { act } from 'react';

import { render } from '@folio/jest-config-stripes/testing-library/react';
import userEvent from '@folio/jest-config-stripes/testing-library/user-event';

import { AccessTypesFilter } from './access-type-filter';

const mockOnStandaloneFilterChange = jest.fn();
const mockOnStandaloneFilterToggle = jest.fn();
const mockHandleStandaloneFilterChange = jest.fn();

const accessTypesStoreData = {
items: {
data: [
{
id: '1',
attributes: { name: 'Open Access' },
},
{
id: '2',
attributes: { name: 'Restricted Access' },
},
],
},
isLoading: false,
isDeleted: false,
};

const dataOptions = [
{ label: 'Open Access', value: 'open-access' },
{ label: 'Restricted Access', value: 'restricted-access' },
];

const renderAccessTypeFilter = (props = {}) => render(
<AccessTypesFilter
accessTypesStoreData={accessTypesStoreData}
searchByAccessTypesEnabled={false}
selectedValues={[]}
onStandaloneFilterChange={mockOnStandaloneFilterChange}
onStandaloneFilterToggle={mockOnStandaloneFilterToggle}
dataOptions={dataOptions}
handleStandaloneFilterChange={mockHandleStandaloneFilterChange}
{...props}
/>
);

describe('AccessTypesFilter', () => {
it('should render loading', () => {
const { getByTestId } = renderAccessTypeFilter({
accessTypesStoreData: {
...accessTypesStoreData,
isLoading: true,
},
});

expect(getByTestId('spinner-ellipsis')).toBeInTheDocument();
});

describe('when there are no access types', () => {
it('should render nothing', () => {
const { container } = renderAccessTypeFilter({
accessTypesStoreData: {
...accessTypesStoreData,
items: { data: [] },
},
});

expect(container).toBeEmptyDOMElement();
});
});

it('should render access types filter', () => {
const { getByText } = renderAccessTypeFilter();

expect(getByText('ui-eholdings.accessTypes.filter')).toBeInTheDocument();
});

describe('when hitting the clear icon', () => {
it('should call onStandaloneFilterChange', async () => {
const { getByRole } = renderAccessTypeFilter({
selectedValues: ['open-access'],
showClearButton: true,
});

await act(() => userEvent.click(getByRole('button', { name: /clear/i })));

expect(mockOnStandaloneFilterChange).toHaveBeenCalledWith({ 'access-type': undefined });
});
});

describe('when searchByAccessTypesEnabled is false', () => {
it('should disable filter', () => {
const { getByRole } = renderAccessTypeFilter({
searchByAccessTypesEnabled: false,
});

expect(getByRole('combobox')).toBeDisabled();
});
});
});
1 change: 1 addition & 0 deletions src/components/access-type-filter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './access-type-filter';
40 changes: 40 additions & 0 deletions src/components/clear-button/clear-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';

import { IconButton } from '@folio/stripes/components';

const propTypes = {
className: PropTypes.string,
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
};

const ClearButton = ({
show,
label,
className,
onClick,
}) => {
const intl = useIntl();

if (!show) {
return null;
}
return (
<IconButton
className={className}
size="small"
iconSize="small"
icon="times-circle-solid"
aria-label={intl.formatMessage({ id: 'stripes-components.filterGroups.clearFilterSetLabel' }, {
labelText: label,
})}
onClick={onClick}
/>
);
};

ClearButton.propTypes = propTypes;

export { ClearButton };
1 change: 1 addition & 0 deletions src/components/clear-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './clear-button';
1 change: 1 addition & 0 deletions src/components/search-by-checkbox/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './search-by-checkbox';
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';

Expand All @@ -13,19 +14,16 @@ const propTypes = {
onStandaloneFilterToggle: PropTypes.func.isRequired,
};

const defaultProps = {
isEnabled: false,
};

const SearchByCheckbox = ({
const SearchByCheckbox = forwardRef(({
filterType,
isEnabled,
isEnabled = false,
onStandaloneFilterToggle,
}) => {
}, ref) => {
const upperFilterType = upperFirst(camelCase(filterType));

return (
<Checkbox
inputRef={ref}
checked={isEnabled}
label={(
<span className={styles['search-warning']}>
Expand All @@ -36,9 +34,8 @@ const SearchByCheckbox = ({
data-test-search-by={filterType}
/>
);
};
});

SearchByCheckbox.propTypes = propTypes;
SearchByCheckbox.defaultProps = defaultProps;

export default SearchByCheckbox;
export { SearchByCheckbox };
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
Accordion,
Icon,
} from '@folio/stripes/components';
import { MultiSelectionFilter } from '@folio/stripes/smart-components';

import SearchByCheckbox from '../search-by-checkbox';
import { AccessTypesFilter } from '../../../access-type-filter';
import { getAccessTypesList } from '../../../utilities';
import { accessTypesReduxStateShape } from '../../../../constants';

import styles from './access-types-filter-accordion.css';
Expand Down Expand Up @@ -49,17 +49,8 @@ const AccessTypesFilterAccordion = ({
'access-type': accessTypes = [],
} = searchFilter;

let accessTypesList = [];

if (accessTypes) {
accessTypesList = Array.isArray(accessTypes)
? accessTypes
: accessTypes.split(',');
}

accessTypesList.sort();

const accessStatusTypesExist = !!accessTypesStoreData?.items?.data?.length;
const accessTypesList = getAccessTypesList(accessTypes);

return accessTypesStoreData?.isLoading
? <Icon icon="spinner-ellipsis" />
Expand All @@ -80,32 +71,15 @@ const AccessTypesFilterAccordion = ({
onToggle={onToggle}
className={styles['search-filter-accordion']}
>
<span className="sr-only" id="accessTypesFilter-label">
<FormattedMessage id="ui-eholdings.settings.accessStatusTypes" />
</span>
<SearchByCheckbox
filterType="access-type"
isEnabled={searchByAccessTypesEnabled}
<AccessTypesFilter
accessTypesStoreData={accessTypesStoreData}
searchByAccessTypesEnabled={searchByAccessTypesEnabled}
selectedValues={accessTypesList}
onStandaloneFilterChange={onStandaloneFilterChange}
onStandaloneFilterToggle={onStandaloneFilterToggle}
dataOptions={dataOptions}
handleStandaloneFilterChange={handleStandaloneFilterChange}
/>
<FormattedMessage id="ui-eholdings.accessTypes.filter">
{
([label]) => (
<div data-testid="search-form-access-type-filter">
<MultiSelectionFilter
id="accessTypeFilterSelect"
ariaLabel={label}
dataOptions={dataOptions}
name="access-type"
onChange={handleStandaloneFilterChange}
selectedValues={accessTypesList}
disabled={!searchByAccessTypesEnabled}
aria-labelledby="accessTypesFilter-label"
/>
</div>
)
}
</FormattedMessage>
</Accordion>
</div>
);
Expand Down

This file was deleted.

Loading

0 comments on commit 68a7c1c

Please sign in to comment.