Skip to content

Commit

Permalink
Merge pull request #355 from AllenInstitute/feature/metadata-editing/…
Browse files Browse the repository at this point in the history
…combobox-bugs

Fix ComboBox (dropdown) bugs
  • Loading branch information
aswallace authored Dec 18, 2024
2 parents 241cd67 + 0c5c47a commit 861e40b
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 33 deletions.
15 changes: 15 additions & 0 deletions packages/core/components/ComboBox/ComboBox.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@
color: unset !important;
}

.combo-box-item-selected button:not(:disabled) {
background-color: var(--secondary-dark);
color: var(--highlight-text-color);
}

.combo-box-item-checked {
background-color: var(--highlight-background-color) !important;
color: var(--highlight-text-color) !important;
}

.combo-box-item button:not(:disabled):hover,
.combo-box-item > div:hover,
.combo-box-item > div:hover :is(input, label) {
Expand Down Expand Up @@ -111,6 +121,11 @@
color: var(--highlight-text-color) !important;
}

.combo-box-item > button::after {
outline: unset !important;
border: unset !important;
}

.options-container {
padding-bottom: var(--margin);
}
35 changes: 7 additions & 28 deletions packages/core/components/ComboBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
import { ComboBox, IComboBoxOption, IRenderFunction, ISelectableOption } from "@fluentui/react";
import classNames from "classnames";
import Fuse from "fuse.js";
import * as React from "react";

import styles from "./ComboBox.module.css";

const FUZZY_SEARCH_OPTIONS = {
// which keys to search on
keys: [{ name: "text", weight: 1.0 }],

// return resulting matches sorted
shouldSort: true,

// arbitrarily tuned; 0.0 requires a perfect match, 1.0 would match anything
threshold: 0.3,
};

interface Props {
className?: string;
selectedKey?: string;
Expand All @@ -34,18 +22,6 @@ interface Props {
export default function BaseComboBox(props: Props) {
const { options, label, placeholder } = props;

const [searchValue, setSearchValue] = React.useState("");

// Fuse logic borrowed from the ListPicker component
const fuse = React.useMemo(() => new Fuse(options, FUZZY_SEARCH_OPTIONS), [options]);
const filteredOptions = React.useMemo(() => {
const filteredRows = searchValue ? fuse.search(searchValue) : options;
return filteredRows.sort((a, b) => {
// If disabled, sort to the bottom
return a.disabled === b.disabled ? 0 : a.disabled ? 1 : -1;
});
}, [options, searchValue, fuse]);

const onRenderItem = (
itemProps: ISelectableOption | undefined,
defaultRender: IRenderFunction<ISelectableOption> | undefined
Expand All @@ -56,6 +32,7 @@ export default function BaseComboBox(props: Props) {
key={`${itemProps.key}-${itemProps.index}`}
className={classNames(styles.comboBoxItem, {
[styles.comboBoxItemDisabled]: !!itemProps.disabled,
[styles.comboBoxItemSelected]: itemProps.key === props.selectedKey,
})}
>
{defaultRender(itemProps)}
Expand All @@ -74,20 +51,22 @@ export default function BaseComboBox(props: Props) {
disabled={props?.disabled}
placeholder={placeholder}
label={label}
openOnKeyboardFocus
multiSelect={props?.multiSelect}
options={filteredOptions}
options={options}
onChange={(_ev, option, _ind, value) => props.onChange?.(option, value)}
onItemClick={(_, option) => props.onChange?.(option)}
onInputValueChange={(value) => {
setSearchValue(value || "");
}}
onRenderItem={(props, defaultRender) => onRenderItem(props, defaultRender)}
scrollSelectedToTop
styles={{
root: styles.comboBox,
label: styles.comboBoxLabel,
callout: styles.comboBoxCallout,
optionsContainer: styles.optionsContainer,
}}
comboBoxOptionStyles={{
rootChecked: styles.comboBoxItemChecked,
}}
useComboBoxAsMenuWidth={props?.useComboBoxAsMenuWidth}
/>
);
Expand Down
7 changes: 5 additions & 2 deletions packages/core/components/DirectoryTree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default function DirectoryTree(props: FileListProps) {
const fileService = useSelector(interaction.selectors.getFileService);
const globalFilters = useSelector(selection.selectors.getFileFilters);
const sortColumn = useSelector(selection.selectors.getSortColumn);
const visibleModal = useSelector(interaction.selectors.getVisibleModal);
const fileSet = React.useMemo(() => {
return new FileSet({
fileService: fileService,
Expand All @@ -53,9 +54,11 @@ export default function DirectoryTree(props: FileListProps) {
// this will effectively clear the current selection in favor of the newly navigated to row.
// If at the beginning or end of a file list and attempting to navigate up or down the file selected & focused will
// be in the file list above or below respectively if possible.
// Should not register key presses when an overlay modal is active
React.useEffect(() => {
const onArrowKeyDown = (event: KeyboardEvent) => {
if (event.code === KeyboardCode.ArrowUp) {
if (!!visibleModal) return;
else if (event.code === KeyboardCode.ArrowUp) {
event.preventDefault(); // Prevent list from scrolling
dispatch(selection.actions.selectNearbyFile("up", event.shiftKey));
} else if (event.code === KeyboardCode.ArrowDown) {
Expand All @@ -66,7 +69,7 @@ export default function DirectoryTree(props: FileListProps) {

window.addEventListener("keydown", onArrowKeyDown, true);
return () => window.removeEventListener("keydown", onArrowKeyDown, true);
}, [dispatch]);
}, [dispatch, visibleModal]);

const {
state: { content, error, isLoading },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import styles from "./EditMetadata.module.css";
interface ExistingAnnotationProps {
onDismiss: () => void;
annotationValueMap: Map<string, any> | undefined;
annotationOptions: { key: string; text: string }[];
annotationOptions: IComboBoxOption[];
selectedFileCount: number;
}

Expand All @@ -22,6 +22,7 @@ interface ExistingAnnotationProps {
export default function ExistingAnnotationPathway(props: ExistingAnnotationProps) {
const [newValues, setNewValues] = React.useState<string>();
const [valueCount, setValueCount] = React.useState<ValueCountItem[]>();
const [selectedAnnotation, setSelectedAnnotation] = React.useState<string | undefined>();

const onSelectMetadataField = (
option: IComboBoxOption | undefined,
Expand All @@ -31,7 +32,11 @@ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps
// FluentUI's combobox doesn't always register the entered value as an option,
// so we need to be able to check both
const selectedFieldName = option?.text || value;
if (!selectedFieldName) return;
if (
!selectedFieldName ||
!props.annotationOptions.some((opt) => opt.key === selectedFieldName)
)
return;
// Track how many values we've seen, since some files may not have a value for this field
let totalValueCount = 0;
if (props?.annotationValueMap?.has(selectedFieldName)) {
Expand All @@ -55,6 +60,7 @@ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps
...valueMap,
];
}
setSelectedAnnotation(selectedFieldName);
setValueCount(valueMap);
};

Expand All @@ -69,11 +75,12 @@ export default function ExistingAnnotationPathway(props: ExistingAnnotationProps
className={styles.comboBox}
label="Select a metadata field"
placeholder="Select a field"
selectedKey={selectedAnnotation}
options={props.annotationOptions}
useComboBoxAsMenuWidth
onChange={onSelectMetadataField}
/>
{valueCount && (
{!!selectedAnnotation && (
<MetadataDetails
onChange={(value) => setNewValues(value)}
items={valueCount || []}
Expand Down

0 comments on commit 861e40b

Please sign in to comment.