diff --git a/src/components/controls/FilterListDropdown/FilterListDropdown.js b/src/components/controls/FilterListDropdown/FilterListDropdown.js
new file mode 100644
index 0000000..74242c6
--- /dev/null
+++ b/src/components/controls/FilterListDropdown/FilterListDropdown.js
@@ -0,0 +1,83 @@
+// This component is a dropdown list from which a user picks an option
+// The option is then supplied to a filter in the data table, which filters
+// the data accordingly.
+// This allows filtering the data on additional attributes without adding
+// more columns to an already-complicated table. Additional columns may
+// be added to the table and hidden, then this dropdown can be used to
+// filter on the hidden column in a space-effient way.
+
+import React, {useEffect, useState} from 'react';
+import {useStore} from '../../../hooks/useZustandStore';
+import map from 'lodash/fp/map';
+import Select from 'react-select';
+
+export default function FilterListDropdown( {filter_id, initialize, label} ) {
+ // list of available filters
+ const filters = useStore((state) => state.filters);
+ const optionLabels = useStore((state) => state.filterOptionLabels);
+ const [items, setItems] = useState([]);
+ const [filterAccess, setFilterAccess] = useState("fake!");
+ const [selected, setSelected] = useState("");
+
+
+ const makeItem = (value) => {
+ if((filter_id in optionLabels) && (value in optionLabels[filter_id])) {
+ return {"value": value, "label": optionLabels[filter_id][value]};
+ }
+ return {"value": value, "label": value}
+ }
+
+
+ const onSelect = (option) => {
+ // set locally so UI updates
+ setSelected(option);
+ // update in table filter
+ filterAccess(option["value"]);
+ }
+
+ //once the access function is available, set the filter to the default. Only
+ // done once.
+ useEffect(() => {
+ if(filterAccess && items.length > 0) {
+ const elem = items.find((option) => option["value"] === initialize);
+ if(elem) {
+ onSelect(elem);
+ }
+ }
+ }, [filterAccess, items]);
+
+ //set up the selector items and callback function, only run once.
+ useEffect(() => {
+ if (filters && (filter_id in filters) && (items.length == 0)) {
+
+ // this bit of minor weirdness is necessary because
+ // if you pass React a function via useState, it
+ // assumes you want it to invoke that function to calculate
+ // the next iteration of the state attribute, and calls it
+ // for you, passing in the current state and storing the output
+ // as the new state.
+ // if you want to actually store the function itself, you need
+ // to work within this functionality by passing a function
+ // that returns the function you actually want to store.
+ // react will invoke the wrapper function, and dutifully
+ // store the result (the desired function).
+ const af = filters[filter_id]["accessFunction"];
+ setFilterAccess(() => af);
+
+ setItems(filters[filter_id]["options"].map(makeItem));
+ }
+ }, [filters]);
+
+
+ return (
+
+
+
+ );
+
+ }
\ No newline at end of file
diff --git a/src/components/controls/FilterListDropdown/package.json b/src/components/controls/FilterListDropdown/package.json
new file mode 100644
index 0000000..8ba0399
--- /dev/null
+++ b/src/components/controls/FilterListDropdown/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "FilterListDropdown",
+ "version": "0.0.0",
+ "private": true,
+ "main": "./FilterListDropdown.js"
+}
diff --git a/src/components/data-grid/column-filters/SelectArrayColumnFilter/SelectArrayColumnFilter.js b/src/components/data-grid/column-filters/SelectArrayColumnFilter/SelectArrayColumnFilter.js
index eda35b5..6a96ada 100644
--- a/src/components/data-grid/column-filters/SelectArrayColumnFilter/SelectArrayColumnFilter.js
+++ b/src/components/data-grid/column-filters/SelectArrayColumnFilter/SelectArrayColumnFilter.js
@@ -1,7 +1,8 @@
-import React from 'react';
+import React, {useEffect} from 'react';
import Form from 'react-bootstrap/Form';
import ClearButton from '../../misc/ClearButton';
import styles from '../ColumnFilters.module.css';
+import {useStore} from '../../../../hooks/useZustandStore';
// Custom filter UI for selecting a unique option from a list that contains
// an array of option values. (Typically this array coalesces information about
@@ -12,6 +13,8 @@ export default function SelectArrayColumnFilter({
toString = option => option.toString(),
allValue = "*",
}) {
+ const registerFilter = useStore.getState().registerFilter;
+
// Calculate the options for filtering using the preFilteredRows.
// The row values themselves are arrays, and each array element is added
// to the list of options.
@@ -25,6 +28,12 @@ export default function SelectArrayColumnFilter({
return [...options.values()];
}, [id, preFilteredRows])
+ // register this filter so it can be used by other UI elements
+ // only needs to be done once.
+ useEffect(() => {
+ registerFilter(id, "list", setFilter, options? options : {});
+ }, [options]);
+
// Render a multi-select box
return (
diff --git a/src/hooks/useZustandStore.js b/src/hooks/useZustandStore.js
new file mode 100644
index 0000000..2747599
--- /dev/null
+++ b/src/hooks/useZustandStore.js
@@ -0,0 +1,31 @@
+import {create} from 'zustand';
+import merge from 'lodash/fp/merge';
+
+export const useStore = create((set) => {
+ return {
+ // stores information about table filters.
+ // this allows them to be accessed from components outside the table
+ // each filter is represent as an object, keyed to a unique identifier.
+ // the object has properties
+ // accessFunction: (to set the filter)
+ // options: (describes possible selectable values
+ // type: describes the "API" of the filter,
+ // ie filters of type "list" have you pick one or more option from a provided set
+ // filters of type "range" have you pick a maximum and minimum value
+ filters: {},
+
+ registerFilter: (key, type, access, options) => {
+ let filter = {}
+ filter[key] = {"options": options, "type": type, "accessFunction": access};
+ set((state) => ({filters: merge(state.filters, filter)}));
+ },
+
+ // labels for filter dropdowns. Each set should be keyed to match an "key" in
+ // .filters; they will be retreived on that basis.
+ filterOptionLabels: {
+ "versions": {"CMIP5": "CMIP5+CWEC2016", "CMIP6": "CMIP6+CWEC2020",},
+ }
+
+
+ }
+})
\ No newline at end of file