Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 5x 144x 144x 144x 259x 3x 3x 144x 8x 3x 144x | import { CaretDownIcon, CaretUpIcon, CheckIcon } from "@phosphor-icons/react";
import GiveButton from "@shared/Button/GiveButton";
import ContextualMenu from "@shared/ContextualMenu/ContextualMenu";
import { GiveSearchBarProps } from "@shared/SearchBar/GiveSearchBar";
import { styled } from "@theme/v2/Provider";
import { useCustomThemeV2 } from "@theme/hooks/useCustomThemeV2";
import { ReactElement, ReactNode, useMemo, useState } from "react";
export interface HubDropdownOption {
value: string;
label: string;
id?: number;
Image?: ReactElement;
}
interface HubDropdownProps {
selectedValue: string;
selectedLabel: string;
selectedImg?: ReactNode;
options: HubDropdownOption[];
onSelect: (option: HubDropdownOption) => void;
searchBarProps?: GiveSearchBarProps;
isLoading?: boolean;
isFetchingNextPage?: boolean;
onEndReached?: () => void;
menuWidth?: number;
disabled?: boolean;
dataTestId?: string;
}
/**
* Controlled dropdown for the Reconciliation Hub filter bar. Unlike the shared
* URL-param-driven `DropdownFilter`, this drives the hub's local filter state:
* selection is reported through `onSelect`. Reuses the shared `GiveButton` +
* `ContextualMenu` primitives for visual parity with the other filters.
*/
const HubDropdown = ({
selectedValue,
selectedLabel,
selectedImg,
options,
onSelect,
searchBarProps,
isLoading,
isFetchingNextPage,
onEndReached,
menuWidth = 278,
disabled = false,
dataTestId,
}: HubDropdownProps) => {
const { isMobileView } = useCustomThemeV2();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const menuOptions = useMemo(
() =>
options.map((option) => ({
text: option.label,
isSelected: option.value === selectedValue,
IconRight: option.value === selectedValue ? CheckIcon : undefined,
Image: option.Image,
onClick: () => {
onSelect(option);
setAnchorEl(null);
},
})),
[options, selectedValue, onSelect],
);
return (
<>
<StyledButton
variant="filled"
color="light"
label={selectedLabel}
size="large"
disabled={disabled}
data-testid={dataTestId}
startIcon={selectedImg}
endIcon={anchorEl ? <CaretUpIcon size={16} /> : <CaretDownIcon size={16} />}
onClick={(e: React.MouseEvent<HTMLElement>) => setAnchorEl(e.currentTarget)}
sx={{ minWidth: "fit-content", height: "40px" }}
/>
<ContextualMenu
menuWidth={menuWidth}
handleClose={() => setAnchorEl(null)}
anchorEl={anchorEl}
color={isMobileView ? "primary" : "transparent"}
texture={isMobileView ? "solid" : "blurred"}
horizontalOrigin="left"
options={menuOptions}
searchBarProps={searchBarProps}
isLoading={isLoading}
isFetchingNextPage={isFetchingNextPage}
onEndReached={onEndReached}
emptySection="empty-search"
/>
</>
);
};
const StyledButton = styled(GiveButton)(({ theme }) => ({
borderRadius: "40px",
whiteSpace: "nowrap",
border: "none",
[theme.breakpoints.down("v2_md")]: {
background: theme.palette.primitive?.transparent?.["darken-5"],
color: theme.palette.text.primary,
},
"&:hover": {
backgroundColor: theme.palette.primitive?.transparent?.["darken-5"],
},
}));
export default HubDropdown;
|