diff --git a/craco.config.js b/craco.config.js
index 60d0905f8..0b1fd60b1 100644
--- a/craco.config.js
+++ b/craco.config.js
@@ -94,6 +94,9 @@ module.exports = {
},
javascriptEnabled: true,
math: 'always',
+ paths: [
+ path.resolve(__dirname, 'node_modules/@cfpb/cfpb-core/src'),
+ ],
},
},
},
diff --git a/cypress/e2e/common/filters.cy.js b/cypress/e2e/common/filters.cy.js
index b23a0cbdb..0b7e99c99 100644
--- a/cypress/e2e/common/filters.cy.js
+++ b/cypress/e2e/common/filters.cy.js
@@ -39,12 +39,16 @@ describe('Filter Panel', () => {
cy.get('#date_received-from').should('be.visible');
cy.log('collapse it');
- cy.get('.date-filter button.a-btn--link:first').click({ force: true });
+ cy.get('.date-filter > button.o-expandable__header:first').click({
+ force: true,
+ });
cy.get('#date-received-agg #start_date').should('not.exist');
cy.log('open it');
- cy.get('.date-filter button.a-btn--link:first').click({ force: true });
+ cy.get('.date-filter > button.o-expandable__header:first').click({
+ force: true,
+ });
cy.log('apply dates');
cy.get('#date_received-from').clear();
diff --git a/package.json b/package.json
index 3026fe234..79cc33f49 100644
--- a/package.json
+++ b/package.json
@@ -32,25 +32,25 @@
},
"browserslist": "> 0.2% in @cfpb/browserslist-config stats",
"devDependencies": {
- "@babel/cli": "^7.24.5",
- "@babel/core": "^7.24.5",
- "@babel/eslint-parser": "^7.24.5",
- "@babel/preset-react": "^7.24.1",
- "@babel/runtime": "^7.24.5",
+ "@babel/cli": "^7.24.7",
+ "@babel/core": "^7.24.7",
+ "@babel/eslint-parser": "^7.24.7",
+ "@babel/preset-react": "^7.24.7",
+ "@babel/runtime": "^7.24.7",
"@cfpb/browserslist-config": "0.0.2",
"@cfpb/cfpb-atomic-component": "1.2.0",
- "@cfpb/cfpb-buttons": "1.2.0",
+ "@cfpb/cfpb-buttons": "1.3.0",
"@cfpb/cfpb-core": "1.2.0",
- "@cfpb/cfpb-design-system": "^1.2.0",
+ "@cfpb/cfpb-design-system": "^1.3.2",
"@cfpb/cfpb-expandables": "^1.2.0",
- "@cfpb/cfpb-forms": "1.2.0",
+ "@cfpb/cfpb-forms": "1.3.0",
"@cfpb/cfpb-grid": "1.2.0",
"@cfpb/cfpb-icons": "^1.2.0",
- "@cfpb/cfpb-layout": "1.2.0",
- "@cfpb/cfpb-notifications": "1.2.0",
- "@cfpb/cfpb-pagination": "1.2.0",
+ "@cfpb/cfpb-layout": "1.3.0",
+ "@cfpb/cfpb-notifications": "1.3.0",
+ "@cfpb/cfpb-pagination": "1.3.0",
"@cfpb/cfpb-tables": "1.2.0",
- "@cfpb/cfpb-typography": "1.2.0",
+ "@cfpb/cfpb-typography": "1.3.2",
"@craco/craco": "^7.1.0",
"@reduxjs/toolkit": "^2.2.5",
"@testing-library/cypress": "^10.0.1",
@@ -61,7 +61,7 @@
"coveralls": "^3.0.9",
"craco-esbuild": "^0.6.1",
"craco-less": "^3.0.1",
- "cypress": "^13.9.0",
+ "cypress": "^13.11.0",
"d3": "^7.9.0",
"d3-selection": "^3.0.0",
"dayjs": "^1.11.10",
@@ -91,7 +91,7 @@
"postcss-less": "^6.0.0",
"postcss-preset-env": "^9.5.13",
"postcss-safe-parser": "^7.0.0",
- "prettier": "^3.2.4",
+ "prettier": "^3.3.1",
"prop-types": "^15.5.10",
"query-string": "^9.0.0",
"react": "^18.2.0",
diff --git a/src/components/Filters/CompanyReceivedFilter.js b/src/components/Filters/CompanyReceivedFilter.js
index eeb108538..317661f98 100644
--- a/src/components/Filters/CompanyReceivedFilter.js
+++ b/src/components/Filters/CompanyReceivedFilter.js
@@ -49,15 +49,6 @@ export const CompanyReceivedFilter = () => {
setThroughDate(validThroughDate);
}, [dateThrough]);
- const handleClear = (period) => {
- if (period === 'from') {
- dispatch(changeDates(fieldName, '', throughDate));
- }
- if (period === 'through') {
- dispatch(changeDates(fieldName, fromDate, ''));
- }
- };
-
const handleKeyDownFromDate = (event) => {
if (event.key === 'Enter') {
fromRef.current.blur();
@@ -115,7 +106,7 @@ export const CompanyReceivedFilter = () => {
dayjs(fromDate).isBefore(minDate) ||
dayjs(fromDate).isAfter(throughDate)
) {
- style.push('a-text-input__error');
+ style.push('a-text-input--error');
}
return style.join(' ');
}, [fromDate, throughDate]);
@@ -126,7 +117,7 @@ export const CompanyReceivedFilter = () => {
dayjs(throughDate).isAfter(maxDate) ||
dayjs(throughDate).isBefore(fromDate)
) {
- style.push('a-text-input__error');
+ style.push('a-text-input--error');
}
return style.join(' ');
}, [fromDate, throughDate]);
@@ -142,29 +133,22 @@ export const CompanyReceivedFilter = () => {
>
From
-
-
setFromDate(evt.target.value)}
- onKeyDown={handleKeyDownFromDate}
- min={minDate}
- max={maxDate}
- ref={fromRef}
- placeholder={DATE_VALIDATION_FORMAT}
- type="date"
- value={fromDate}
- />
-
+
@@ -174,43 +158,38 @@ export const CompanyReceivedFilter = () => {
>
Through
-
-
setThroughDate(evt.target.value)}
- onKeyDown={handleKeyDownThroughDate}
- min={minDate}
- max={maxDate}
- placeholder={DATE_VALIDATION_FORMAT}
- ref={throughRef}
- type="date"
- value={throughDate}
- />
-
+
- {errors.length
- ? errors.map((message, key) => (
+ {errors.length ? (
+
+ {errors.map((message, key) => (
{message}
{getIcon('delete-round', 'cf-icon-delete-round')}
- ))
- : null}
+ ))}
+
+ ) : null}
);
diff --git a/src/components/Filters/DateFilter.js b/src/components/Filters/DateFilter.js
index ccb9ea111..89be28754 100644
--- a/src/components/Filters/DateFilter.js
+++ b/src/components/Filters/DateFilter.js
@@ -49,24 +49,15 @@ export const DateFilter = () => {
useEffect(() => {
// put it in YYYY-MM-DD format
// validate to make sure it's not invalid
- const validFromDate = dateFrom ? formatDate(dateFrom) : '';
+ const validFromDate = dateFrom ? formatDate(dateFrom) : minDate;
setFromDate(validFromDate);
}, [dateFrom]);
useEffect(() => {
- const validThroughDate = dateThrough ? formatDate(dateThrough) : '';
+ const validThroughDate = dateThrough ? formatDate(dateThrough) : maxDate;
setThroughDate(validThroughDate);
}, [dateThrough]);
- const handleClear = (period) => {
- if (period === 'from') {
- dispatch(changeDates(fieldName, minDate, throughDate));
- }
- if (period === 'through') {
- dispatch(changeDates(fieldName, fromDate, maxDate));
- }
- };
-
const handleKeyDownFromDate = (event) => {
if (event.key === 'Enter') {
fromRef.current.blur();
@@ -118,7 +109,7 @@ export const DateFilter = () => {
dayjs(fromDate).isAfter(throughDate) ||
dayjs(fromDate).isSame(throughDate)
) {
- style.push('a-text-input__error');
+ style.push('a-text-input--error');
}
return style.join(' ');
}, [fromDate, throughDate]);
@@ -130,7 +121,7 @@ export const DateFilter = () => {
dayjs(throughDate).isBefore(fromDate) ||
dayjs(throughDate).isSame(fromDate)
) {
- style.push('a-text-input__error');
+ style.push('a-text-input--error');
}
return style.join(' ');
}, [fromDate, throughDate]);
@@ -159,29 +150,25 @@ export const DateFilter = () => {
>
From
-
-
- 'From' date must be less than 'through' date
-
-
@@ -174,33 +161,24 @@ exports[`component::CompanyReceivedFilter snapshot supports no dates in the Redu
From
-
-
+
+
@@ -211,33 +189,24 @@ exports[`component::CompanyReceivedFilter snapshot supports no dates in the Redu
Through
-
-
+
+
diff --git a/src/components/Filters/__tests__/__snapshots__/DateFilter.spec.js.snap b/src/components/Filters/__tests__/__snapshots__/DateFilter.spec.js.snap
index 709fb948f..39c2d983a 100644
--- a/src/components/Filters/__tests__/__snapshots__/DateFilter.spec.js.snap
+++ b/src/components/Filters/__tests__/__snapshots__/DateFilter.spec.js.snap
@@ -60,33 +60,24 @@ exports[`component::DateFilter snapshot shows a warning for April 2017 1`] = `
From
-
-
+
+
@@ -97,33 +88,24 @@ exports[`component::DateFilter snapshot shows a warning for April 2017 1`] = `
Through
-
-
+
+
@@ -229,33 +211,24 @@ exports[`component::DateFilter snapshot shows errors 1`] = `
From
-
-
+
+
@@ -266,33 +239,24 @@ exports[`component::DateFilter snapshot shows errors 1`] = `
Through
-
-
+
+
@@ -417,33 +381,24 @@ exports[`component::DateFilter snapshot supports no dates in the Redux Store 1`]
From
-
-
+
+
@@ -454,33 +409,24 @@ exports[`component::DateFilter snapshot supports no dates in the Redux Store 1`]
Through
-
-
+
+
diff --git a/src/components/Search/PillPanel.less b/src/components/Search/PillPanel.less
index 05b3ec534..58f944d72 100644
--- a/src/components/Search/PillPanel.less
+++ b/src/components/Search/PillPanel.less
@@ -34,7 +34,7 @@
@media @phone {
flex-direction: column;
- margin-top: 10px;
+ margin-top: 70px;
.layout-row {
padding-left: 0;
}
diff --git a/src/components/Search/SearchBar.js b/src/components/Search/SearchBar.js
index ae1beb533..81199f96c 100644
--- a/src/components/Search/SearchBar.js
+++ b/src/components/Search/SearchBar.js
@@ -31,7 +31,6 @@ export const SearchBar = ({ debounceWait }) => {
// handleClear is called whenever the user submits by pressing enter
// shouldCallClear prevents handleClear from firing a reset after the search is set
const [shouldCallClear, setShouldCallClear] = useState(true);
- const isVisible = Boolean(searchText || inputValue);
useEffect(() => {
setInputValue(searchText);
@@ -113,7 +112,7 @@ export const SearchBar = ({ debounceWait }) => {
-
-
Skip to Results
diff --git a/src/components/Search/SearchPanel.less b/src/components/Search/SearchPanel.less
index 479f5694c..92e52521f 100644
--- a/src/components/Search/SearchPanel.less
+++ b/src/components/Search/SearchPanel.less
@@ -8,6 +8,7 @@
@media @phone {
padding: @gutter-narrow;
+ min-height: 150px;
}
background-color: var(--gray-5);
}
diff --git a/src/components/Typeahead/AsyncTypeahead/AsyncTypeahead.js b/src/components/Typeahead/AsyncTypeahead/AsyncTypeahead.js
index 5ae369b3b..7dbda6ce7 100644
--- a/src/components/Typeahead/AsyncTypeahead/AsyncTypeahead.js
+++ b/src/components/Typeahead/AsyncTypeahead/AsyncTypeahead.js
@@ -8,19 +8,19 @@ import { ClearButton } from '../ClearButton/ClearButton';
export const AsyncTypeahead = ({
ariaLabel,
- className,
- defaultValue,
- delayWait,
+ defaultValue = '',
+ delayWait = 0,
htmlId,
- isDisabled,
+ isDisabled = false,
handleChange,
handleClear,
handleSearch,
- hasClearButton,
- maxResults,
- minLength,
+ hasClearButton = false,
+ hasSearchButton = false,
+ maxResults = 5,
+ minLength = 2,
options,
- placeholder,
+ placeholder = 'Enter your search text',
}) => {
const ref = useRef();
const [searchValue, setSearchValue] = useState(defaultValue);
@@ -43,55 +43,65 @@ export const AsyncTypeahead = ({
};
return (
-
-
-
- {getIcon('search')}
-
-
-
{
- if (input === '') setIsVisible(false);
- else setIsVisible(true);
- }}
- onSearch={(input) => {
- setSearchValue(input);
- handleSearch(input);
- }}
- onChange={(selected) => {
- handleChange(selected);
- ref.current.clear();
- setSearchValue('');
- }}
- options={options}
- maxResults={maxResults}
- placeholder={placeholder}
- renderMenuItemChildren={(option) => (
-
-
-
- )}
- />
- {!!isVisible && (
- {
- handleTypeaheadClear();
- setIsVisible(false);
+
+
+
+
+ {
+ if (input === '') setIsVisible(false);
+ else setIsVisible(true);
+ }}
+ onSearch={(input) => {
+ setSearchValue(input);
+ handleSearch(input);
+ }}
+ onChange={(selected) => {
+ handleChange(selected);
+ ref.current.clear();
+ setSearchValue('');
}}
+ options={options}
+ maxResults={maxResults}
+ placeholder={placeholder}
+ renderMenuItemChildren={(option) => (
+
+
+
+ )}
/>
+
+ {!!isVisible && (
+ {
+ handleTypeaheadClear();
+ setIsVisible(false);
+ }}
+ />
+ )}
+
+ {!!hasSearchButton && (
+
)}
@@ -100,28 +110,17 @@ export const AsyncTypeahead = ({
AsyncTypeahead.propTypes = {
ariaLabel: PropTypes.string.isRequired,
- className: PropTypes.string,
defaultValue: PropTypes.string,
delayWait: PropTypes.number.isRequired,
- isDisabled: PropTypes.bool.isRequired,
+ isDisabled: PropTypes.bool,
handleChange: PropTypes.func.isRequired,
handleClear: PropTypes.func,
handleSearch: PropTypes.func.isRequired,
hasClearButton: PropTypes.bool,
+ hasSearchButton: PropTypes.bool,
htmlId: PropTypes.string.isRequired,
maxResults: PropTypes.number,
minLength: PropTypes.number,
options: PropTypes.array,
placeholder: PropTypes.string,
};
-
-AsyncTypeahead.defaultProps = {
- className: '',
- defaultValue: '',
- delayWait: 0,
- hasClearButton: false,
- isDisabled: false,
- maxResults: 5,
- minLength: 2,
- placeholder: 'Enter your search text',
-};
diff --git a/src/components/Typeahead/ClearButton/ClearButton.js b/src/components/Typeahead/ClearButton/ClearButton.js
index cc1727528..2afacce87 100644
--- a/src/components/Typeahead/ClearButton/ClearButton.js
+++ b/src/components/Typeahead/ClearButton/ClearButton.js
@@ -4,11 +4,12 @@ import getIcon from '../../iconMap';
export const ClearButton = ({ onClear }) => {
return (
);
};
diff --git a/src/components/Typeahead/HighlightingOption/HighlightingOption.js b/src/components/Typeahead/HighlightingOption/HighlightingOption.js
index 9d4f42014..d6f0c68c2 100644
--- a/src/components/Typeahead/HighlightingOption/HighlightingOption.js
+++ b/src/components/Typeahead/HighlightingOption/HighlightingOption.js
@@ -7,13 +7,11 @@ export const HighlightingOption = ({ label, position, value }) => {
}
const start = label.substring(0, position);
- const match = label.substring(position, value.length);
const end = label.substring(position + value.length);
-
return (
{start}
- {match}
+ {value}
{end}
);
diff --git a/src/components/Typeahead/HighlightingOption/HighlightingOption.spec.js b/src/components/Typeahead/HighlightingOption/HighlightingOption.spec.js
index 384dd6840..979a56e09 100644
--- a/src/components/Typeahead/HighlightingOption/HighlightingOption.spec.js
+++ b/src/components/Typeahead/HighlightingOption/HighlightingOption.spec.js
@@ -8,7 +8,7 @@ describe('HighlightingOption', () => {
,
);
- expect(container.innerHTML).toEqual(`Maryland (MD)`);
+ expect(container.innerHTML).toEqual(`maryland (MD)`);
});
test('Handles position < 0', () => {
@@ -18,4 +18,12 @@ describe('HighlightingOption', () => {
expect(container.innerHTML).toEqual(`Maryland (MD)`);
});
+
+ test('Renders partially bolded text in the middle', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container.innerHTML).toEqual(`Maryland (MD)`);
+ });
});
diff --git a/src/components/Typeahead/Input/Input.js b/src/components/Typeahead/Input/Input.js
index 3fd1e8490..4a0a32f34 100644
--- a/src/components/Typeahead/Input/Input.js
+++ b/src/components/Typeahead/Input/Input.js
@@ -1,5 +1,3 @@
-import '../Typeahead.less';
-import React from 'react';
import PropTypes from 'prop-types';
import getIcon from '../../iconMap';
import { ClearButton } from '../ClearButton/ClearButton';
@@ -12,35 +10,38 @@ export const Input = ({
handleChange,
handleClear,
handlePressEnter,
- isClearVisible,
placeholder,
value,
}) => {
return (
-
-
-
+
+
-
- {!!isClearVisible &&
}
+
-
+
+
);
};
Input.propTypes = {
@@ -51,13 +52,11 @@ Input.propTypes = {
handleClear: PropTypes.func,
handlePressEnter: PropTypes.func,
htmlId: PropTypes.string.isRequired,
- isClearVisible: PropTypes.bool,
placeholder: PropTypes.string,
value: PropTypes.string.isRequired,
};
Input.defaultProps = {
- isClearVisible: false,
isDisabled: false,
placeholder: 'Enter your search text',
};
diff --git a/src/components/Typeahead/Input/Input.spec.js b/src/components/Typeahead/Input/Input.spec.js
index 8bc4241e3..46b9ddbdc 100644
--- a/src/components/Typeahead/Input/Input.spec.js
+++ b/src/components/Typeahead/Input/Input.spec.js
@@ -26,7 +26,7 @@ describe('Input', () => {
test('Handle when change and enter are called', () => {
renderComponent();
- const input = screen.getByRole('textbox');
+ const input = screen.getByRole('searchbox');
fireEvent.change(input, { target: { value: 'text' } });
expect(handleChangeMock).toBeCalled();
fireEvent.click(input);
@@ -34,16 +34,10 @@ describe('Input', () => {
expect(handleEnterMock).toBeCalled();
});
- test('When handleClear is not given then clear button is not present', () => {
- renderComponent();
- expect(screen.queryByRole('button')).not.toBeInTheDocument();
- });
-
- test('When handleClear is given then clear button is present', () => {
+ test('When handleClear is given then clear button is present', () => {
const handleClearMock = jest.fn();
renderComponent(handleClearMock, true);
- expect(screen.getByRole('button')).toBeDefined();
- fireEvent.click(screen.getByRole('button'));
+ fireEvent.click(screen.getByRole('button', { name: 'clear search' }));
expect(handleClearMock).toBeCalled();
});
});
diff --git a/src/components/Typeahead/Typeahead.less b/src/components/Typeahead/Typeahead.less
index c5e404ab9..93104ee6d 100644
--- a/src/components/Typeahead/Typeahead.less
+++ b/src/components/Typeahead/Typeahead.less
@@ -6,21 +6,10 @@
border: solid 2px var(--pacific);
}
}
- .m-btn-inside-input {
- .a-btn {
- position: absolute;
- right: 1em;
- top: ~'calc(50% - 8px)';
- }
- .cf-icon-svg {
- padding-right: @gutter-minimum * 0.8;
- }
- }
&-selector {
+ width: 100%;
position: relative;
- // z-index: 99;
-
ul {
padding-left: 0;
border: solid 2px var(--pacific);
@@ -100,6 +89,13 @@
background-color: var(--gray-10);
}
}
+
+ .o-search-input__input {
+ //display: block;
+ &-label {
+ z-index: 1;
+ }
+ }
}
.input-contains-label__before {
diff --git a/src/components/Typeahead/Typeahead/Typeahead.js b/src/components/Typeahead/Typeahead/Typeahead.js
index aee44d62a..401cd1393 100644
--- a/src/components/Typeahead/Typeahead/Typeahead.js
+++ b/src/components/Typeahead/Typeahead/Typeahead.js
@@ -1,19 +1,17 @@
import '../Typeahead.less';
-import React, { useRef, useState } from 'react';
+import { ClearButton } from '../ClearButton/ClearButton';
+import { useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { Typeahead as DropdownTypeahead } from 'react-bootstrap-typeahead';
import getIcon from '../../iconMap';
import HighlightingOption from '../HighlightingOption/HighlightingOption';
-import { ClearButton } from '../ClearButton/ClearButton';
export const Typeahead = ({
ariaLabel,
- className,
htmlId,
isDisabled,
handleChange,
handleInputChange,
- hasClearButton,
maxResults,
minLength,
options,
@@ -21,7 +19,6 @@ export const Typeahead = ({
}) => {
const ref = useRef();
const [input, setInput] = useState('');
- const isVisible = hasClearButton && input;
const handleClear = () => {
ref.current.clear();
@@ -29,53 +26,55 @@ export const Typeahead = ({
};
return (
-
-
-
- {getIcon('search')}
+
+
+
+
+ {
+ handleChange(selection);
+ handleClear();
+ }}
+ onInputChange={(value) => {
+ handleInputChange(value);
+ setInput(value);
+ }}
+ options={options}
+ maxResults={maxResults}
+ placeholder={placeholder}
+ renderMenuItemChildren={(option) => (
+
+
+
+ )}
+ inputProps={{
+ id: htmlId,
+ className: 'a-text-input a-text-input__full',
+ }}
+ />
+ {!!input && }
-
-
{
- handleChange(selection);
- handleClear();
- }}
- onInputChange={(value) => {
- handleInputChange(value);
- setInput(value);
- }}
- options={options}
- maxResults={maxResults}
- placeholder={placeholder}
- renderMenuItemChildren={(option) => (
-
-
-
- )}
- />
- {!!isVisible && }
);
};
Typeahead.propTypes = {
ariaLabel: PropTypes.string.isRequired,
- className: PropTypes.string,
isDisabled: PropTypes.bool.isRequired,
handleChange: PropTypes.func.isRequired,
handleInputChange: PropTypes.func.isRequired,
- hasClearButton: PropTypes.bool,
htmlId: PropTypes.string.isRequired,
maxResults: PropTypes.number,
minLength: PropTypes.number,
@@ -84,8 +83,6 @@ Typeahead.propTypes = {
};
Typeahead.defaultProps = {
- className: '',
- hasClearButton: false,
isDisabled: false,
maxResults: 5,
minLength: 2,