Skip to content

Commit 22d646f

Browse files
authored
Merge pull request #237 from fedspendingtransparency/mod/11625-move-new-picker
Mod/11625 move new picker
2 parents b9ce2be + 37f3c1f commit 22d646f

File tree

39 files changed

+550
-35
lines changed

39 files changed

+550
-35
lines changed

components/NewPicker.jsx

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/**
2+
* NewPicker.jsx
3+
* Created by Nick Torres 2/7/2024
4+
*/
5+
6+
import React, { useRef, useState, useEffect } from 'react';
7+
import PropTypes from 'prop-types';
8+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
9+
import { uniqueId } from 'lodash';
10+
11+
require('../styles/components/_newPicker.scss');
12+
13+
const propTypes = {
14+
size: PropTypes.oneOf(['sm', 'md', 'lg', 'small', 'medium', 'large']),
15+
label: PropTypes.string,
16+
leftIcon: PropTypes.oneOfType([PropTypes.string, PropTypes.element, PropTypes.object]),
17+
sortFn: PropTypes.func,
18+
selectedOption: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
19+
classname: PropTypes.string,
20+
dropdownClassname: PropTypes.string,
21+
buttonClassname: PropTypes.string,
22+
minTextWidth: PropTypes.string,
23+
id: PropTypes.string,
24+
options: PropTypes.arrayOf(PropTypes.shape({
25+
name: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.number]),
26+
value: PropTypes.any,
27+
onClick: PropTypes.func,
28+
classNames: PropTypes.string
29+
})),
30+
children: PropTypes.node,
31+
enabled: PropTypes.bool,
32+
parentWidth: PropTypes.number,
33+
infoSection: PropTypes.bool,
34+
infoSectionContent: PropTypes.string
35+
};
36+
37+
const defaultSort = (a, b, selectedOption) => {
38+
// if no sort fn is provided, sort active element to lowest index
39+
if (a.name === selectedOption) return -1;
40+
if (b.name === selectedOption) return 1;
41+
// then, sort alphabetically
42+
if (a.name < b.name) return -1;
43+
if (a.name > b.name) return 1;
44+
return 0;
45+
};
46+
47+
const NewPicker = ({
48+
size,
49+
label = '',
50+
children,
51+
leftIcon,
52+
enabled,
53+
id = '',
54+
options,
55+
selectedOption,
56+
dropdownClassname = '',
57+
buttonClassname = '',
58+
minTextWidth = '',
59+
classname = '',
60+
sortFn = defaultSort,
61+
parentWidth,
62+
infoSection = false,
63+
infoSectionContent = ''
64+
}) => {
65+
const pickerRef = useRef(null);
66+
const buttonRef = useRef(null);
67+
const [expanded, setExpanded] = useState(false);
68+
const [isEnabled, setIsEnabled] = useState(enabled || false);
69+
const fontAwesomeIconId = "usa-dt-picker__button-icon--svg";
70+
71+
const height = infoSection ? '310px' : 'initial';
72+
73+
const toggleMenu = (e) => {
74+
e.preventDefault();
75+
setExpanded(!expanded);
76+
};
77+
78+
const keyUp = (e) => {
79+
if (e.key === "Escape" && expanded) {
80+
setExpanded(!expanded);
81+
}
82+
};
83+
84+
const handleSort = (a, b) => sortFn(a, b, selectedOption);
85+
86+
const createOnClickFn = (cb) => (param) => {
87+
cb(param);
88+
setExpanded(false);
89+
};
90+
91+
let variation = '';
92+
if (size === 'sm' || size === 'small') {
93+
variation = '-sm';
94+
}
95+
else if (size === 'md' || size === 'medium') {
96+
variation = '-md';
97+
}
98+
else if (size === 'lg' || size === 'large') {
99+
variation = '-lg';
100+
}
101+
102+
useEffect(() => {
103+
const closeMenu = (e) => {
104+
if ((
105+
expanded
106+
&& pickerRef.current
107+
&& !pickerRef.current.contains(e.target)
108+
&& e.target.id !== `${id}-${fontAwesomeIconId}`
109+
&& e.target.parentNode.id !== `${id}-${fontAwesomeIconId}`
110+
)) {
111+
setExpanded(false);
112+
}
113+
};
114+
115+
document.addEventListener('click', closeMenu);
116+
117+
return () => {
118+
document.removeEventListener('click', closeMenu);
119+
};
120+
}, [expanded, id]);
121+
122+
useEffect(() => {
123+
setIsEnabled(enabled);
124+
}, [enabled]);
125+
126+
return (
127+
<div className={`filter__dropdown-container ${classname}`} ref={pickerRef}>
128+
{label !== '' && <span className={`filter__dropdown-label${variation}`}>{label}</span>}
129+
<div className="filter__dropdown-button-list-container">
130+
<button
131+
className={`filter__dropdown-button${variation} ${isEnabled ? 'enabled' : 'not-enabled'} ${buttonClassname}`}
132+
ref={buttonRef}
133+
aria-label="Filter Dropdown Button"
134+
onClick={toggleMenu}
135+
onKeyUp={keyUp}
136+
style={{ maxWidth: `${parentWidth}px` }}
137+
type="button">
138+
{leftIcon
139+
&& (
140+
<span className="filter__dropdown-left-icon">
141+
<FontAwesomeIcon icon={leftIcon} alt="page title bar button icon" />
142+
</span>
143+
)}
144+
{children || (
145+
<span className={`filter__dropdown-button-text ${minTextWidth}`}>
146+
{selectedOption}
147+
</span>
148+
)}
149+
<span className="filter__dropdown-chevron">
150+
{!expanded && (
151+
<FontAwesomeIcon icon="chevron-down" alt="Toggle menu" />
152+
)}
153+
{expanded && (
154+
<FontAwesomeIcon icon="chevron-up" alt="Toggle menu" />
155+
)}
156+
</span>
157+
</button>
158+
{expanded
159+
&& (
160+
<div className="filter__dropdown__list-info-wrapper" style={{ maxWidth: `${parentWidth}px` }}>
161+
<ul className={`filter__dropdown-list${variation} ${expanded ? '' : 'hide'} ${isEnabled ? 'enabled' : 'not-enabled'} ${dropdownClassname}`} style={{ maxWidth: `${parentWidth}px`, height }}>
162+
{options?.sort(handleSort)
163+
.map((option) => ({
164+
...option,
165+
onClick: createOnClickFn(option.onClick)
166+
}))
167+
.map((option) => (
168+
<li
169+
key={uniqueId()}
170+
className={`filter__dropdown-list-item ${option?.classNames ? option.classNames : ''} ${option.name?.trim() === selectedOption?.trim() ? 'active' : ''}`}>
171+
<button
172+
style={{ display: "block", width: "100%" }}
173+
tabIndex={0}
174+
onClick={(e) => {
175+
e.preventDefault();
176+
option.onClick(option.value);
177+
}}
178+
onKeyUp={(e) => {
179+
e.preventDefault();
180+
if (e.key === "Enter") {
181+
option.onClick(option.value);
182+
}
183+
}}
184+
className="filter__dropdown-item"
185+
type="button">
186+
{option.component ? option.component : option.name}
187+
</button>
188+
</li>
189+
))}
190+
{infoSection
191+
&& (
192+
<li>
193+
<div className="filter__dropdown-explainer" style={{ width: `${parentWidth}px` }}>
194+
<div className="filter__dropdownSeparator" />
195+
<div className="filter__dropdown-content">{infoSectionContent}</div>
196+
</div>
197+
</li>
198+
)}
199+
</ul>
200+
201+
</div>
202+
)}
203+
</div>
204+
</div>
205+
);
206+
};
207+
208+
NewPicker.propTypes = propTypes;
209+
export default NewPicker;

dist/data-transparency-ui.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/6543.699857db3e50ff615ea7.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/iframe.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,4 @@
508508

509509
import './3301.897021de.iframe.bundle.js';
510510

511-
import './main.7b6cd4cc.iframe.bundle.js';</script></body></html>
511+
import './main.bf6ca2f9.iframe.bundle.js';</script></body></html>

docs/main.a3489fde28d1171dd9dc.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"generatedAt":1745855969490,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":false,"refCount":0,"testPackages":{"@babel/plugin-proposal-optional-chaining":"7.21.0","@testing-library/jest-dom":"6.5.0","@testing-library/react":"14.0.0","eslint-plugin-jasmine":"2.10.1","jest":"28.1.3","jest-cli":"28.1.3"},"hasRouterPackage":false,"packageManager":{"type":"npm","agent":"npm"},"typescriptOptions":{"reactDocgen":"react-docgen-typescript"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-webpack5","options":{}},"builder":"@storybook/builder-webpack5","renderer":"@storybook/react","portableStoriesFileCount":5,"applicationFileCount":9,"storybookVersion":"8.6.12","storybookVersionSpecifier":"^8.6.12","language":"typescript","storybookPackages":{"@storybook/addon-console":{"version":"3.0.0"},"@storybook/manager-api":{"version":"8.6.12"},"@storybook/react":{"version":"8.6.12"},"@storybook/react-webpack5":{"version":"8.6.12"},"@storybook/source-loader":{"version":"8.6.12"},"@storybook/test":{"version":"8.6.12"},"@storybook/theming":{"version":"8.6.12"},"storybook":{"version":"8.6.12"}},"addons":{"@storybook/addon-docs":{"version":"8.6.12"},"@storybook/blocks":{"version":"8.6.12"},"@storybook/addon-a11y":{"version":"8.6.12"},"@storybook/addon-viewport":{"version":"8.6.12"},"@storybook/addon-actions":{"version":"8.6.12"},"@storybook/addon-essentials":{"version":"8.6.12"},"@storybook/addon-controls":{"version":"8.6.12"},"@chromatic-com/storybook":{"version":"3.2.5"},"@storybook/addon-webpack5-compiler-babel":{"version":"3.0.5"},"@storybook/addon-interactions":{"version":"8.6.12"}}}
1+
{"generatedAt":1747687021497,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":false,"refCount":0,"testPackages":{"@babel/plugin-proposal-optional-chaining":"7.21.0","@testing-library/jest-dom":"6.5.0","@testing-library/react":"14.0.0","eslint-plugin-jasmine":"2.10.1","jest":"28.1.3","jest-cli":"28.1.3"},"hasRouterPackage":false,"packageManager":{"type":"npm","agent":"npm"},"typescriptOptions":{"reactDocgen":"react-docgen-typescript"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-webpack5","options":{}},"builder":"@storybook/builder-webpack5","renderer":"@storybook/react","portableStoriesFileCount":5,"applicationFileCount":9,"storybookVersion":"8.6.12","storybookVersionSpecifier":"^8.6.12","language":"typescript","storybookPackages":{"@storybook/addon-console":{"version":"3.0.0"},"@storybook/manager-api":{"version":"8.6.12"},"@storybook/react":{"version":"8.6.12"},"@storybook/react-webpack5":{"version":"8.6.12"},"@storybook/source-loader":{"version":"8.6.12"},"@storybook/test":{"version":"8.6.12"},"@storybook/theming":{"version":"8.6.12"},"storybook":{"version":"8.6.12"}},"addons":{"@storybook/addon-docs":{"version":"8.6.12"},"@storybook/blocks":{"version":"8.6.12"},"@storybook/addon-a11y":{"version":"8.6.12"},"@storybook/addon-viewport":{"version":"8.6.12"},"@storybook/addon-actions":{"version":"8.6.12"},"@storybook/addon-essentials":{"version":"8.6.12"},"@storybook/addon-controls":{"version":"8.6.12"},"@chromatic-com/storybook":{"version":"3.2.5"},"@storybook/addon-webpack5-compiler-babel":{"version":"3.0.5"},"@storybook/addon-interactions":{"version":"8.6.12"}}}

0 commit comments

Comments
 (0)