@haniffalab/cherita-react 1.2.0-dev.2025-04-29.181538d6 → 1.2.0-dev.2025-05-16.4367ee63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cjs/components/full-page/FullPage.js +19 -26
  2. package/dist/cjs/components/full-page/FullPagePseudospatial.js +20 -26
  3. package/dist/cjs/components/offcanvas/index.js +1 -2
  4. package/dist/cjs/components/scatterplot/Scatterplot.js +8 -2
  5. package/dist/cjs/components/scatterplot/SpatialControls.js +46 -17
  6. package/dist/cjs/components/scatterplot/Toolbox.js +1 -18
  7. package/dist/cjs/components/search-bar/SearchBar.js +152 -57
  8. package/dist/cjs/components/search-bar/SearchInfo.js +175 -0
  9. package/dist/cjs/components/search-bar/SearchResults.js +58 -33
  10. package/dist/cjs/components/var-list/VarItem.js +70 -0
  11. package/dist/cjs/components/var-list/VarList.js +7 -91
  12. package/dist/cjs/components/var-list/VarSet.js +42 -16
  13. package/dist/cjs/utils/Legend.js +14 -8
  14. package/dist/css/cherita.css +60 -10
  15. package/dist/css/cherita.css.map +1 -1
  16. package/dist/esm/components/full-page/FullPage.js +20 -27
  17. package/dist/esm/components/full-page/FullPagePseudospatial.js +21 -27
  18. package/dist/esm/components/offcanvas/index.js +1 -2
  19. package/dist/esm/components/scatterplot/Scatterplot.js +8 -2
  20. package/dist/esm/components/scatterplot/SpatialControls.js +47 -18
  21. package/dist/esm/components/scatterplot/Toolbox.js +1 -18
  22. package/dist/esm/components/search-bar/SearchBar.js +153 -59
  23. package/dist/esm/components/search-bar/SearchInfo.js +165 -0
  24. package/dist/esm/components/search-bar/SearchResults.js +60 -35
  25. package/dist/esm/components/var-list/VarItem.js +70 -2
  26. package/dist/esm/components/var-list/VarList.js +7 -91
  27. package/dist/esm/components/var-list/VarSet.js +44 -18
  28. package/dist/esm/utils/Legend.js +14 -8
  29. package/package.json +2 -2
  30. package/scss/cherita.scss +44 -5
  31. package/scss/components/layouts.scss +20 -13
  32. package/scss/components/lists.scss +11 -0
  33. package/scss/components/plots.scss +3 -5
@@ -1,14 +1,18 @@
1
- import React, { useState, useEffect, useMemo, useDeferredValue } from "react";
1
+ import React, { useDeferredValue, useEffect, useMemo, useState } from "react";
2
+ import { faPlus } from "@fortawesome/free-solid-svg-icons";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2
4
  import _ from "lodash";
3
- import { Dropdown } from "react-bootstrap";
5
+ import { Button, ListGroup } from "react-bootstrap";
4
6
  import { useDatasetDispatch } from "../../context/DatasetContext";
5
7
  import { useDiseaseSearch, useVarSearch } from "../../utils/search";
6
8
  import { VirtualizedList } from "../../utils/VirtualizedList";
7
9
  export function VarSearchResults(_ref) {
8
10
  let {
9
11
  text,
10
- setShowSuggestions,
11
- handleSelect
12
+ handleSelect,
13
+ selectedResult,
14
+ setSelectedResult,
15
+ setResultsLength
12
16
  } = _ref;
13
17
  const [suggestions, setSuggestions] = useState([]);
14
18
  const dispatch = useDatasetDispatch();
@@ -33,51 +37,68 @@ export function VarSearchResults(_ref) {
33
37
  });
34
38
  } else {
35
39
  setSuggestions([]);
36
- setShowSuggestions(false);
37
40
  }
38
41
  };
39
42
  return _.debounce(setData, 300);
40
- }, [setParams, setShowSuggestions]);
43
+ }, [setParams]);
41
44
  useEffect(() => {
42
45
  updateParams(text);
43
46
  }, [text, updateParams]);
44
47
  useEffect(() => {
45
48
  if (!isPending && !serverError) {
46
49
  setSuggestions(fetchedData);
47
- setShowSuggestions(true);
50
+ setResultsLength(fetchedData?.length);
48
51
  }
49
- }, [fetchedData, isPending, serverError, setShowSuggestions]);
52
+ }, [fetchedData, isPending, serverError, setResultsLength]);
50
53
  const getDataAtIndex = index => deferredData[index];
51
- const ItemComponent = item => /*#__PURE__*/React.createElement(Dropdown.Item, {
52
- key: item.name,
53
- as: "button",
54
+ const ItemComponent = item => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
55
+ className: "virtualized-list-wrapper"
56
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, {
57
+ key: item,
58
+ onClick: () => {
59
+ setSelectedResult(item);
60
+ },
61
+ active: selectedResult?.index === item.index
62
+ }, /*#__PURE__*/React.createElement("div", {
63
+ className: "d-flex justify-content-between align-items-center w-100"
64
+ }, /*#__PURE__*/React.createElement("div", null, item.name), /*#__PURE__*/React.createElement("div", {
65
+ className: "d-flex align-items-center gap-1"
66
+ }, /*#__PURE__*/React.createElement(Button, {
67
+ type: "button",
68
+ className: "m-0 p-0 px-1",
69
+ variant: "outline-secondary",
70
+ title: "Add to list",
54
71
  disabled: isStale,
55
72
  onClick: () => {
56
73
  handleSelect(dispatch, item);
57
- _.delay(() => {
58
- setShowSuggestions(false);
59
- }, 150);
60
74
  }
61
- }, item.name);
62
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Dropdown.Header, null, "Features"), /*#__PURE__*/React.createElement("div", {
75
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
76
+ icon: faPlus
77
+ })))))));
78
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
63
79
  className: "search-results"
80
+ }, /*#__PURE__*/React.createElement(ListGroup, {
81
+ variant: "flush",
82
+ className: "cherita-list"
64
83
  }, deferredData?.length ? /*#__PURE__*/React.createElement(VirtualizedList, {
65
84
  getDataAtIndex: getDataAtIndex,
66
85
  count: deferredData.length,
67
86
  ItemComponent: ItemComponent,
68
87
  overscan: 500,
69
- estimateSize: 32,
70
- maxHeight: "25vh"
71
- }) : /*#__PURE__*/React.createElement(Dropdown.Item, {
88
+ estimateSize: 42,
89
+ maxHeight: "70vh"
90
+ }) : /*#__PURE__*/React.createElement(ListGroup.Item, {
72
91
  key: "empty",
73
92
  as: "button",
74
93
  disabled: true
75
- }, !serverError ? isStale || isPending ? "Loading..." : "No items found" : "Failed to fetch data")));
94
+ }, !text.length ? "Search features" : !serverError ? isStale || isPending ? "Loading..." : "No items found" : "Failed to fetch data"))));
76
95
  }
77
96
  export function DiseasesSearchResults(_ref2) {
78
97
  let {
79
98
  text,
80
- setShowSuggestions
99
+ selectedResult,
100
+ setSelectedResult,
101
+ setResultsLength
81
102
  } = _ref2;
82
103
  const [suggestions, setSuggestions] = useState([]);
83
104
  const dispatch = useDatasetDispatch();
@@ -112,37 +133,41 @@ export function DiseasesSearchResults(_ref2) {
112
133
  useEffect(() => {
113
134
  if (!isPending && !serverError) {
114
135
  setSuggestions(fetchedData);
115
- setShowSuggestions(true);
136
+ setResultsLength(fetchedData?.length);
116
137
  }
117
- }, [fetchedData, isPending, serverError, setShowSuggestions]);
138
+ }, [fetchedData, isPending, serverError, setResultsLength]);
118
139
  const getDataAtIndex = index => deferredData[index];
119
- const ItemComponent = item => /*#__PURE__*/React.createElement(Dropdown.Item, {
140
+ const ItemComponent = item => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
141
+ className: "virtualized-list-wrapper"
142
+ }, /*#__PURE__*/React.createElement(ListGroup.Item, {
120
143
  key: item.name,
121
- as: "button",
122
- disabled: isStale,
123
144
  onClick: () => {
145
+ setSelectedResult(item);
124
146
  dispatch({
125
147
  type: "select.disease",
126
148
  id: item.disease_id,
127
149
  name: item.disease_name
128
150
  });
129
- _.delay(() => {
130
- setShowSuggestions(false);
131
- }, 150);
132
- }
133
- }, item.disease_name);
134
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Dropdown.Header, null, "Diseases"), /*#__PURE__*/React.createElement("div", {
151
+ },
152
+ active: selectedResult?.id === item.id
153
+ }, /*#__PURE__*/React.createElement("div", {
154
+ className: "d-flex justify-content-between align-items-center w-100"
155
+ }, /*#__PURE__*/React.createElement("div", null, item.disease_name)))));
156
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
135
157
  className: "search-results"
158
+ }, /*#__PURE__*/React.createElement(ListGroup, {
159
+ variant: "flush",
160
+ className: "cherita-list"
136
161
  }, deferredData?.length ? /*#__PURE__*/React.createElement(VirtualizedList, {
137
162
  getDataAtIndex: getDataAtIndex,
138
163
  count: deferredData.length,
139
164
  ItemComponent: ItemComponent,
140
165
  overscan: 250,
141
166
  estimateSize: 32,
142
- maxHeight: "25vh"
143
- }) : /*#__PURE__*/React.createElement(Dropdown.Item, {
167
+ maxHeight: "70vh"
168
+ }) : /*#__PURE__*/React.createElement(ListGroup.Item, {
144
169
  key: "empty",
145
170
  as: "button",
146
171
  disabled: true
147
- }, !serverError ? isStale || isPending ? "Loading..." : "No items found" : "Failed to fetch data")));
172
+ }, !text.length ? "Search diseases" : !serverError ? isStale || isPending ? "Loading..." : "No items found" : "Failed to fetch data"))));
148
173
  }
@@ -8,7 +8,7 @@ import { COLOR_ENCODINGS, SELECTION_MODES } from "../../constants/constants";
8
8
  import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
9
9
  import { useFilteredData } from "../../context/FilterContext";
10
10
  import { Histogram } from "../../utils/Histogram";
11
- import { useFetch, useDebouncedFetch } from "../../utils/requests";
11
+ import { useDebouncedFetch, useFetch } from "../../utils/requests";
12
12
  import { VirtualizedList } from "../../utils/VirtualizedList";
13
13
  function VarHistogram(_ref) {
14
14
  let {
@@ -75,7 +75,7 @@ function VarDiseaseInfoItem(item) {
75
75
  }
76
76
  }))));
77
77
  }
78
- function VarDiseaseInfo(_ref2) {
78
+ export function VarDiseaseInfo(_ref2) {
79
79
  let {
80
80
  data
81
81
  } = _ref2;
@@ -255,4 +255,72 @@ export function VarItem(_ref5) {
255
255
  } else {
256
256
  return null;
257
257
  }
258
+ }
259
+ export function SearchResultItem(_ref6) {
260
+ let {
261
+ item,
262
+ isActive,
263
+ selectVar,
264
+ removeVar,
265
+ isDiseaseGene = false,
266
+ showSetColorEncoding = true,
267
+ showRemove = true
268
+ } = _ref6;
269
+ const ENDPOINT = "disease/gene";
270
+ const [openInfo, setOpenInfo] = useState(false);
271
+ const dataset = useDataset();
272
+ const params = {
273
+ geneName: item.name,
274
+ diseaseDatasets: dataset.diseaseDatasets
275
+ };
276
+ const isNotInData = item.matrix_index === -1;
277
+ const {
278
+ fetchedData,
279
+ isPending,
280
+ serverError
281
+ } = useFetch(ENDPOINT, params, {
282
+ refetchOnMount: false,
283
+ enabled: !!dataset.diseaseDatasets.length
284
+ });
285
+ const hasDiseaseInfo = !isPending && !serverError && !!fetchedData?.length;
286
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
287
+ className: `d-flex justify-content-between ${hasDiseaseInfo ? "cursor-pointer" : ""}`,
288
+ onClick: () => {
289
+ setOpenInfo(o => !o);
290
+ }
291
+ }, /*#__PURE__*/React.createElement("div", {
292
+ className: "d-flex justify-content-between align-items-center w-100"
293
+ }, /*#__PURE__*/React.createElement("div", null, item.name), /*#__PURE__*/React.createElement("div", {
294
+ className: "d-flex align-items-center gap-1"
295
+ }, hasDiseaseInfo && /*#__PURE__*/React.createElement(MoreVert, null), showSetColorEncoding && /*#__PURE__*/React.createElement(Button, {
296
+ type: "button",
297
+ key: item.matrix_index,
298
+ variant: isActive ? "primary" : isNotInData ? "outline-secondary" : "outline-primary",
299
+ className: "m-0 p-0 px-1",
300
+ onClick: e => {
301
+ e.stopPropagation();
302
+ selectVar();
303
+ },
304
+ disabled: isNotInData,
305
+ title: isNotInData ? "Not present in data" : "Set as color encoding"
306
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
307
+ icon: faDroplet
308
+ })), (!isDiseaseGene || !showRemove) && /*#__PURE__*/React.createElement(Button, {
309
+ type: "button",
310
+ className: "m-0 p-0 px-1",
311
+ variant: "outline-secondary",
312
+ title: "Remove from list",
313
+ onClick: e => {
314
+ e.stopPropagation();
315
+ removeVar();
316
+ }
317
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
318
+ icon: faTrash
319
+ }))))), hasDiseaseInfo && /*#__PURE__*/React.createElement(Collapse, {
320
+ in: openInfo
321
+ }, /*#__PURE__*/React.createElement("div", {
322
+ className: "mt-2 var-disease-info-collapse"
323
+ }, /*#__PURE__*/React.createElement(VarDiseaseInfo, {
324
+ data: fetchedData
325
+ }))));
258
326
  }
@@ -4,13 +4,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
4
  import _ from "lodash";
5
5
  import { Alert, Button, ListGroup } from "react-bootstrap";
6
6
  import ButtonGroup from "react-bootstrap/ButtonGroup";
7
+ import { VarItem } from "./VarItem";
8
+ import { VarListToolbar } from "./VarListToolbar";
9
+ import { VarSet } from "./VarSet";
7
10
  import { SELECTION_MODES, VAR_SORT } from "../../constants/constants";
8
11
  import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
9
12
  import { LoadingSpinner } from "../../utils/LoadingIndicators";
10
13
  import { useFetch } from "../../utils/requests";
11
- import { VarItem } from "./VarItem";
12
- import { VarListToolbar } from "./VarListToolbar";
13
- import { VarSet } from "./VarSet";
14
14
  const useVarMean = function (varKeys) {
15
15
  let enabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
16
16
  const ENDPOINT = "matrix/mean";
@@ -45,93 +45,11 @@ const useVarMean = function (varKeys) {
45
45
  const sortMeans = (i, means) => {
46
46
  return means[i.name] || _.min(_.values(means)) - 1;
47
47
  };
48
-
49
- // @TODO: display where disease data comes from
50
- // add to disease dataset metadata
51
- function DiseaseVarList(_ref) {
52
- let {
53
- makeListItem
54
- } = _ref;
55
- const ENDPOINT = "disease/genes";
56
- const dataset = useDataset();
57
- const dispatch = useDatasetDispatch();
58
- const [diseaseVars, setDiseaseVars] = useState([]);
59
- const [sortedDiseaseVars, setSortedDiseaseVars] = useState([]);
60
- const [params, setParams] = useState({
61
- url: dataset.url,
62
- col: dataset.varNamesCol,
63
- diseaseId: dataset.selectedDisease?.id,
64
- diseaseDatasets: dataset.diseaseDatasets
65
- });
66
- useEffect(() => {
67
- setParams(p => {
68
- return {
69
- ...p,
70
- diseaseId: dataset.selectedDisease?.id
71
- };
72
- });
73
- }, [dataset.selectedDisease]);
74
- const diseaseData = useFetch(ENDPOINT, params, {
75
- enabled: !!params.diseaseId,
76
- refetchOnMount: false
77
- });
78
- useEffect(() => {
79
- if (!diseaseData.isPending && !diseaseData.serverError) {
80
- setDiseaseVars(diseaseData.fetchedData);
81
- }
82
- }, [diseaseData.fetchedData, diseaseData.isPending, diseaseData.serverError]);
83
- const varMeans = useVarMean(diseaseVars, !!diseaseVars?.length && dataset.varSort.disease.sort === VAR_SORT.MATRIX);
84
- useEffect(() => {
85
- if (dataset.varSort.disease.sort === VAR_SORT.MATRIX) {
86
- if (!varMeans.isPending && !varMeans.serverError) {
87
- setSortedDiseaseVars(_.orderBy(diseaseVars, o => {
88
- return sortMeans(o, varMeans.fetchedData);
89
- }, dataset.varSort.disease.sortOrder));
90
- }
91
- } else if (dataset.varSort.disease.sort === VAR_SORT.NAME) {
92
- setSortedDiseaseVars(_.orderBy(diseaseVars, "name", dataset.varSort.disease.sortOrder));
93
- } else {
94
- setSortedDiseaseVars(diseaseVars);
95
- }
96
- }, [dataset.varSort.disease.sort, dataset.varSort.disease.sortOrder, diseaseVars, varMeans.fetchedData, varMeans.isPending, varMeans.serverError]);
97
- const diseaseVarList = _.map(sortedDiseaseVars, item => {
98
- return makeListItem(item, true);
99
- });
100
- const isPending = diseaseData.isPending || varMeans.isPending && dataset.varSort.disease.sort === VAR_SORT.MATRIX;
101
- return /*#__PURE__*/React.createElement(React.Fragment, null, dataset.selectedDisease && (!isPending && !diseaseVars?.length ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
102
- className: "d-flex justify-content-between mt-3"
103
- }, /*#__PURE__*/React.createElement("h5", null, "Disease genes")), /*#__PURE__*/React.createElement(Alert, {
104
- variant: "light"
105
- }, "No disease genes found.")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
106
- className: "d-flex justify-content-between my-2"
107
- }, /*#__PURE__*/React.createElement("h5", null, "Disease genes"), /*#__PURE__*/React.createElement(ButtonGroup, {
108
- "aria-label": "Feature options",
109
- size: "sm"
110
- }, /*#__PURE__*/React.createElement(Button, {
111
- variant: "info",
112
- onClick: () => {
113
- dispatch({
114
- type: "reset.disease"
115
- });
116
- }
117
- }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
118
- icon: faTimes,
119
- className: "me-1"
120
- }), "Clear"))), /*#__PURE__*/React.createElement("p", null, dataset.selectedDisease?.name), /*#__PURE__*/React.createElement(VarListToolbar, {
121
- varType: "disease"
122
- }), /*#__PURE__*/React.createElement("div", {
123
- className: "position-relative"
124
- }, isPending && /*#__PURE__*/React.createElement(LoadingSpinner, null), /*#__PURE__*/React.createElement(ListGroup, {
125
- variant: "flush",
126
- className: "cherita-list"
127
- }, diseaseVarList)))));
128
- }
129
- export function VarNamesList(_ref2) {
48
+ export function VarNamesList(_ref) {
130
49
  let {
131
50
  mode = SELECTION_MODES.SINGLE,
132
- displayName = "genes",
133
- showDiseaseVarList = true
134
- } = _ref2;
51
+ displayName = "genes"
52
+ } = _ref;
135
53
  const dataset = useDataset();
136
54
  const dispatch = useDatasetDispatch();
137
55
  const [varButtons, setVarButtons] = useState(mode === SELECTION_MODES.SINGLE ? dataset.selectedVar ? _.unionWith([dataset.selectedVar], dataset.varSets, _.isEqual) : [...dataset.varSets] : [...dataset.selectedMultiVar, ...dataset.varSets]);
@@ -285,7 +203,5 @@ export function VarNamesList(_ref2) {
285
203
  }, isPending && /*#__PURE__*/React.createElement(LoadingSpinner, null), /*#__PURE__*/React.createElement(ListGroup, {
286
204
  variant: "flush",
287
205
  className: "cherita-list"
288
- }, varList)))), /*#__PURE__*/React.createElement(React.Fragment, null, showDiseaseVarList && /*#__PURE__*/React.createElement(DiseaseVarList, {
289
- makeListItem: makeListItem
290
- }))));
206
+ }, varList))))));
291
207
  }
@@ -1,13 +1,12 @@
1
1
  import React, { useState } from "react";
2
- import { faCircleInfo, faDroplet, faTrash } from "@fortawesome/free-solid-svg-icons";
2
+ import { faChevronDown, faChevronUp, faCircleInfo, faDroplet, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
3
3
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
- import { List } from "@mui/icons-material";
5
4
  import _ from "lodash";
6
5
  import { Button, Collapse, ListGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
6
+ import { SingleSelectionItem } from "./VarItem";
7
7
  import { COLOR_ENCODINGS, SELECTION_MODES } from "../../constants/constants";
8
8
  import { useDataset, useDatasetDispatch } from "../../context/DatasetContext";
9
- import { SearchBar } from "../search-bar/SearchBar";
10
- import { SingleSelectionItem } from "./VarItem";
9
+ import { SearchModal } from "../search-bar/SearchBar";
11
10
 
12
11
  // @TODO: add button to score genes and plot
13
12
 
@@ -24,10 +23,11 @@ function SingleSelectionSet(_ref) {
24
23
  isActive,
25
24
  selectSet,
26
25
  removeSet,
27
- removeSetVar,
28
- showSearchBar = true
26
+ removeSetVar
29
27
  } = _ref;
30
28
  const [openSet, setOpenSet] = useState(false);
29
+ const [showModal, setShowModal] = useState(false);
30
+ const [searchText, setSearchText] = useState("");
31
31
  const varList = set.vars.length ? _.map(set.vars, v => {
32
32
  return /*#__PURE__*/React.createElement(ListGroup.Item, {
33
33
  key: v.name
@@ -46,16 +46,38 @@ function SingleSelectionSet(_ref) {
46
46
  }
47
47
  }, /*#__PURE__*/React.createElement("div", {
48
48
  className: "d-flex justify-content-between align-items-center w-100"
49
- }, /*#__PURE__*/React.createElement("div", null, set.name), /*#__PURE__*/React.createElement("div", {
49
+ }, /*#__PURE__*/React.createElement("div", {
50
+ className: "ellipsis-text",
51
+ title: set.name
52
+ }, set.name), /*#__PURE__*/React.createElement("div", {
50
53
  className: "d-flex align-items-center gap-1"
51
54
  }, /*#__PURE__*/React.createElement(OverlayTrigger, {
52
55
  placement: "top",
53
56
  overlay: /*#__PURE__*/React.createElement(Tooltip, null, "This set represents the mean value of its features")
54
57
  }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
55
58
  icon: faCircleInfo
56
- })), /*#__PURE__*/React.createElement(List, null), /*#__PURE__*/React.createElement(Button, {
59
+ })), /*#__PURE__*/React.createElement(Button, {
60
+ type: "button",
61
+ variant: "outline-primary",
62
+ className: "m-0 p-0 px-1",
63
+ disabled: !set.vars.length,
64
+ title: "Open set"
65
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
66
+ icon: openSet ? faChevronUp : faChevronDown
67
+ })), /*#__PURE__*/React.createElement(Button, {
68
+ type: "button",
69
+ variant: "outline-primary",
70
+ className: "m-0 p-0 px-1",
71
+ onClick: e => {
72
+ e.stopPropagation();
73
+ setShowModal(true);
74
+ },
75
+ disabled: !set.vars.length,
76
+ title: "Add to set"
77
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
78
+ icon: faPlus
79
+ })), /*#__PURE__*/React.createElement(Button, {
57
80
  type: "button",
58
- key: set.name,
59
81
  variant: isActive ? "primary" : "outline-primary",
60
82
  className: "m-0 p-0 px-1",
61
83
  onClick: e => {
@@ -81,17 +103,21 @@ function SingleSelectionSet(_ref) {
81
103
  in: openSet
82
104
  }, /*#__PURE__*/React.createElement("div", {
83
105
  className: "mt-2"
84
- }, showSearchBar &&
85
- /*#__PURE__*/
86
- // @TODO: fix how results are displayed, should be placed on top of parent components
87
- React.createElement(SearchBar, {
88
- handleSelect: (d, i) => addVarToSet(d, set, i)
89
- }), /*#__PURE__*/React.createElement("div", {
90
- className: "mx-2"
91
106
  }, /*#__PURE__*/React.createElement(ListGroup, {
92
107
  variant: "flush",
93
- className: "cherita-list"
94
- }, varList)))));
108
+ className: "cherita-list var-set-list"
109
+ }, varList))), /*#__PURE__*/React.createElement(SearchModal, {
110
+ show: showModal,
111
+ handleClose: () => setShowModal(false),
112
+ text: searchText,
113
+ setText: setSearchText,
114
+ displayText: "features",
115
+ handleSelect: (d, i) => {
116
+ addVarToSet(d, set, i);
117
+ },
118
+ searchVar: true,
119
+ searchDiseases: false
120
+ }));
95
121
  }
96
122
  function MultipleSelectionSet(_ref2) {
97
123
  let {
@@ -1,9 +1,11 @@
1
1
  import React, { useMemo } from "react";
2
+ import { faDroplet } from "@fortawesome/free-solid-svg-icons";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2
4
  import _ from "lodash";
3
- import { formatNumerical, FORMATS } from "./string";
4
5
  import { COLOR_ENCODINGS } from "../constants/constants";
5
6
  import { useDataset } from "../context/DatasetContext";
6
7
  import { rgbToHex, useColor } from "../helpers/color-helper";
8
+ import { formatNumerical, FORMATS } from "./string";
7
9
  export function Legend(_ref) {
8
10
  let {
9
11
  isCategorical = false,
@@ -39,20 +41,24 @@ export function Legend(_ref) {
39
41
  className: "gradient"
40
42
  }, /*#__PURE__*/React.createElement("p", {
41
43
  className: "small m-0 p-0"
42
- }, (dataset.colorEncoding === COLOR_ENCODINGS.VAR ? dataset.selectedVar?.name : dataset.selectedObs?.name) + addText), spanList, /*#__PURE__*/React.createElement("span", {
44
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
45
+ icon: faDroplet,
46
+ className: "me-1"
47
+ }), (dataset.colorEncoding === COLOR_ENCODINGS.VAR ? dataset.selectedVar?.name : dataset.selectedObs?.name) + addText), spanList, /*#__PURE__*/React.createElement("span", {
43
48
  className: "domain-min"
44
49
  }, formatNumerical(min, FORMATS.EXPONENTIAL)), /*#__PURE__*/React.createElement("span", {
45
50
  className: "domain-med"
46
51
  }, formatNumerical((min + max) * 0.5, FORMATS.EXPONENTIAL)), /*#__PURE__*/React.createElement("span", {
47
52
  className: "domain-max"
48
53
  }, formatNumerical(max, FORMATS.EXPONENTIAL))));
49
- } else {
54
+ } else if (dataset.colorEncoding === COLOR_ENCODINGS.OBS && dataset.selectedObs) {
50
55
  return /*#__PURE__*/React.createElement("div", {
51
- className: "cherita-legend"
52
- }, /*#__PURE__*/React.createElement("div", {
53
- className: "gradient"
56
+ className: "cherita-legend categorical"
54
57
  }, /*#__PURE__*/React.createElement("p", {
55
- className: "small m-0 p-0"
56
- }, dataset.colorEncoding === COLOR_ENCODINGS.OBS && dataset.selectedObs ? dataset.selectedObs.name : "")));
58
+ className: "legend-text text-end m-0 p-0"
59
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
60
+ icon: faDroplet,
61
+ className: "me-2"
62
+ }), dataset.selectedObs.name));
57
63
  }
58
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haniffalab/cherita-react",
3
- "version": "1.2.0-dev.2025-04-29.181538d6",
3
+ "version": "1.2.0-dev.2025-05-16.4367ee63",
4
4
  "author": "Haniffa Lab",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -124,5 +124,5 @@
124
124
  "url": "https://github.com/haniffalab/cherita-react/issues"
125
125
  },
126
126
  "homepage": "https://github.com/haniffalab/cherita-react#readme",
127
- "prereleaseSha": "181538d6c7cc4cd43ce73f241e370abcc21ab301"
127
+ "prereleaseSha": "4367ee63df5209cda070f21e714ac5c6ccaa1e2c"
128
128
  }
package/scss/cherita.scss CHANGED
@@ -73,15 +73,13 @@ $prefix: "bs-" !default;
73
73
  .cherita-spatial-footer {
74
74
  position: absolute;
75
75
  z-index: 10;
76
- bottom: 1rem;
76
+ bottom: 0;
77
77
  display: flex;
78
78
  justify-content: space-between;
79
- align-items: flex-end;
79
+ align-items: end;
80
80
  flex-wrap: wrap;
81
81
  width: 100%;
82
- padding-left: 1rem;
83
- padding-right: 1rem;
84
- padding-bottom: 1rem;
82
+ padding: 1rem;
85
83
  pointer-events: none;
86
84
 
87
85
  > * {
@@ -105,6 +103,41 @@ $prefix: "bs-" !default;
105
103
  .cherita-legend {
106
104
  order: 2;
107
105
  width: 12rem;
106
+ color: #333;
107
+ }
108
+
109
+ .cherita-legend.categorical {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: flex-end;
113
+ padding-left: 1rem;
114
+ flex: 1;
115
+ overflow: hidden;
116
+ white-space: nowrap;
117
+ text-overflow: ellipsis;
118
+ pointer-events: auto;
119
+ .legend-text {
120
+ overflow: hidden;
121
+ text-overflow: ellipsis;
122
+ white-space: nowrap;
123
+ max-width: 100%;
124
+ display: inline-block;
125
+ }
126
+ }
127
+
128
+ @media (max-width: 600px) {
129
+ .cherita-spatial-footer {
130
+ flex-direction: column;
131
+ align-items: center;
132
+ justify-content: center;
133
+ }
134
+
135
+ .cherita-legend {
136
+ flex-direction: column;
137
+ align-items: center;
138
+ margin-top: 1rem;
139
+ padding: 0;
140
+ }
108
141
  }
109
142
 
110
143
  .cherita-accordion-active .accordion-button {
@@ -362,3 +395,9 @@ $gauge-padding: 0.15rem;
362
395
  margin-right: 0.4rem;
363
396
  width: 1rem;
364
397
  }
398
+
399
+ .ellipsis-text {
400
+ white-space: nowrap;
401
+ overflow: hidden;
402
+ text-overflow: ellipsis;
403
+ }
@@ -2,19 +2,21 @@
2
2
  position: relative;
3
3
  }
4
4
 
5
- .cherita-app .cherita-navbar {
6
- @extend .m-3;
7
- @extend .d-block;
8
- @extend .d-xl-none;
9
-
10
- position: absolute;
11
- z-index: 11;
12
- top: 0;
13
- left: 0;
14
- right: 0;
15
- border-radius: 0.25rem;
16
- @media (min-width: 992px) and (max-width: 1199px) {
17
- margin-left: calc(33.33333333% + 1rem) !important;
5
+ .cherita-app {
6
+ .cherita-navbar {
7
+ @extend .m-3;
8
+ @extend .d-block;
9
+ @extend .d-xl-none;
10
+
11
+ position: absolute;
12
+ z-index: 11;
13
+ top: 0;
14
+ left: 0;
15
+ right: 0;
16
+ border-radius: 0.25rem;
17
+ @media (min-width: 992px) and (max-width: 1199px) {
18
+ margin-left: calc(33.33333333% + 1rem) !important;
19
+ }
18
20
  }
19
21
  }
20
22
 
@@ -100,3 +102,8 @@
100
102
  }
101
103
  }
102
104
  }
105
+
106
+ .search-modal-info {
107
+ overflow-y: auto;
108
+ border-left: 1px solid #dee2e6;
109
+ }
@@ -13,6 +13,10 @@
13
13
  color: var(--#{$prefix}body-color);
14
14
  background-color: var(--#{$prefix}tertiary-bg);
15
15
  border-radius: var(--#{$prefix}border-radius);
16
+ cursor: pointer;
17
+ }
18
+ .list-group-item.active {
19
+ background-color: var(--#{$prefix}secondary-bg);
16
20
  }
17
21
  }
18
22
  .obs-statistics {
@@ -24,3 +28,10 @@
24
28
  background-color: var(--#{$prefix}tertiary-bg);
25
29
  border-radius: var(--#{$prefix}border-radius);
26
30
  }
31
+
32
+ .cherita-list.var-set-list {
33
+ .list-group-item {
34
+ padding-right: 0;
35
+ padding-left: 0.35rem;
36
+ }
37
+ }