Skip to content

Commit 717a7d4

Browse files
authored
Merge pull request #145 from UW-Macrostrat/postgrest-scroll-group
Postgrest scroll grouping
2 parents 8cc2fce + 3d925a5 commit 717a7d4

File tree

5 files changed

+161
-28
lines changed

5 files changed

+161
-28
lines changed

packages/ui-components/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Changelog
22

3-
## [4.4.0] -
3+
## [4.4.1] - 2025-07-18
4+
5+
For PostgRESTInfiniteScrollView
6+
7+
- Multiselect disapears if only one searchColumn is inputted
8+
- Handles grouping
9+
10+
## [4.4.0] - 2025-07-10
411

512
For PostgRESTInfiniteScrollView
613

packages/ui-components/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@macrostrat/ui-components",
3-
"version": "4.4.0",
3+
"version": "4.4.1",
44
"description": "UI components for React and Blueprint.js",
55
"main": "dist/cjs/index.js",
66
"module": "dist/esm/index.js",
@@ -46,6 +46,7 @@
4646
"@blueprintjs/datetime2": "^2.3.11",
4747
"@blueprintjs/select": "^5.3.10",
4848
"@macrostrat/hyper": "^3.0.6",
49+
"@macrostrat/map-interface": "workspace:^",
4950
"@types/react": "^18.3.12",
5051
"@types/react-dom": "^18",
5152
"axios": "^1.7.9",

packages/ui-components/src/postgrest-infinite-scroll.ts

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useMemo, useState } from "react";
55
import { MultiSelect, ItemRenderer, ItemPredicate } from "@blueprintjs/select";
66
import { MenuItem, Spinner, InputGroup } from "@blueprintjs/core";
77
import styles from "./postgrest.module.sass";
8+
import { ExpansionPanel } from "@macrostrat/map-interface";
89

910
const h = hyper.styled(styles);
1011

@@ -32,6 +33,8 @@ interface PostgRESTInfiniteScrollProps extends InfiniteScrollProps<any> {
3233
tagInputProps: any;
3334
popoverProps: any;
3435
}>;
36+
group_key?: string;
37+
groups?: Array<{ value: string; label: string }>;
3538
}
3639

3740
export function PostgRESTInfiniteScrollView(
@@ -54,6 +57,7 @@ export function PostgRESTInfiniteScrollView(
5457
key,
5558
toggles = null,
5659
searchColumns = undefined,
60+
group_key = undefined,
5761
...rest
5862
} = props;
5963

@@ -236,35 +240,50 @@ export function PostgRESTInfiniteScrollView(
236240
h(SearchBarToUse, {
237241
onChange: (value) => setFilterValue(value || ""),
238242
}),
239-
h(MultiSelectToUse, {
240-
items: keys.filter((item) => !selectedItems.includes(item.value)),
241-
itemRenderer,
242-
itemPredicate: filterItem,
243-
selectedItems,
244-
onItemSelect: handleSelect,
245-
tagRenderer: (value) => {
246-
const found = keys.find((k) => k.value === value);
247-
return found ? found.label : value;
248-
},
249-
onRemove: handleRemove,
250-
tagInputProps: {
243+
h.if(searchColumns == null || searchColumns.length > 1)(
244+
MultiSelectToUse,
245+
{
246+
items: keys.filter((item) => !selectedItems.includes(item.value)),
247+
itemRenderer,
248+
itemPredicate: filterItem,
249+
selectedItems,
250+
onItemSelect: handleSelect,
251+
tagRenderer: (value) => {
252+
const found = keys.find((k) => k.value === value);
253+
return found ? found.label : value;
254+
},
251255
onRemove: handleRemove,
252-
placeholder: "Select a column(s) to filter by...",
256+
tagInputProps: {
257+
onRemove: handleRemove,
258+
placeholder: "Select a column(s) to filter by...",
259+
},
260+
popoverProps: { minimal: true },
253261
},
254-
popoverProps: { minimal: true },
255-
}),
262+
),
256263
]),
257264
h.if(toggles)("div.toggles", toggles),
258265
]),
259-
h(InfiniteScrollView, {
260-
...rest,
261-
route,
262-
getNextParams: getNextParams ?? defaultGetNextParams,
263-
params: params ?? defaultParams,
264-
initialItems: newInitialItems,
265-
hasMore: hasMore ?? defaultHasMore,
266-
key: newKey,
267-
}),
266+
group_key
267+
? Grouping({
268+
group_key,
269+
groups: props.groups ?? [],
270+
route,
271+
id_key,
272+
params: defaultParams,
273+
getNextParams: getNextParams ?? defaultGetNextParams,
274+
hasMore: hasMore ?? defaultHasMore,
275+
key: newKey,
276+
rest,
277+
})
278+
: h(InfiniteScrollView, {
279+
...rest,
280+
route,
281+
getNextParams: getNextParams ?? defaultGetNextParams,
282+
params: params ?? defaultParams,
283+
initialItems: newInitialItems,
284+
hasMore: hasMore ?? defaultHasMore,
285+
key: newKey,
286+
}),
268287
]);
269288
}
270289

@@ -282,3 +301,80 @@ function SearchBar({ onChange, placeholder = "Search..." }) {
282301
leftIcon: "search",
283302
});
284303
}
304+
305+
interface GroupingProps {
306+
group_key: string;
307+
groups: Array<{ value: string; label: string }>;
308+
route: string;
309+
id_key: string;
310+
params?: Record<string, any>;
311+
getNextParams?: (
312+
response: any[],
313+
params: Record<string, any>,
314+
) => Record<string, any>;
315+
hasMore?: (response: any[]) => boolean;
316+
key?: string;
317+
rest?: any;
318+
}
319+
320+
function Grouping(props: GroupingProps) {
321+
const {
322+
group_key,
323+
groups,
324+
route,
325+
id_key,
326+
params,
327+
getNextParams,
328+
hasMore,
329+
rest,
330+
} = props;
331+
332+
return h("div.group-page", [
333+
groups.map((group) => {
334+
if (!group.value || !group.label) {
335+
throw new Error("Each group must have a value and label");
336+
}
337+
338+
return h(GroupPanel, {
339+
group,
340+
route,
341+
id_key,
342+
params: {
343+
...params,
344+
[group_key]: "eq." + group.value,
345+
},
346+
getNextParams,
347+
hasMore,
348+
...rest,
349+
});
350+
}),
351+
]);
352+
}
353+
354+
function GroupPanel(props) {
355+
const { group, route, params, getNextParams, hasMore, key, ...rest } = props;
356+
357+
const data = useAPIResult(route, {
358+
...params,
359+
limit: 1,
360+
});
361+
362+
if (!data || data?.length === 0) return null;
363+
364+
return h(
365+
ExpansionPanel,
366+
{
367+
title: group.label,
368+
},
369+
[
370+
h(InfiniteScrollView, {
371+
key: key || group.value,
372+
route,
373+
params,
374+
getNextParams,
375+
hasMore,
376+
...rest,
377+
}),
378+
],
379+
);
380+
}

packages/ui-components/stories/postgrest-infinite-scroll.stories.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,43 @@ Primary.args = {
2828
searchColumns: [{ value: "name", label: "Name" }],
2929
};
3030

31-
export const CustomMultiselect = Template.bind({});
31+
export const Multiselect = Template.bind({});
3232

33-
CustomMultiselect.args = {
33+
Multiselect.args = {
3434
limit: 5,
3535
id_key: "source_id",
3636
filterable: true,
3737
ascending: true,
3838
route: "https://dev.macrostrat.org/api/pg/sources_metadata",
3939
delay: 100,
4040
toggles: h("h1", "Toggles here"),
41+
searchColumns: [
42+
{ value: "name", label: "Name" },
43+
{ value: "authors", label: "Author" },
44+
],
45+
};
46+
47+
export const Grouping = Template.bind({});
48+
49+
Grouping.args = {
50+
limit: 20,
51+
id_key: "id",
52+
filterable: true,
53+
ascending: true,
54+
route: "https://dev2.macrostrat.org/api/pg/autocomplete",
55+
delay: 100,
56+
toggles: h("h1", "Toggles here"),
4157
searchColumns: [{ value: "name", label: "Name" }],
58+
group_key: "type",
59+
groups: [
60+
{ value: "minerals", label: "Minerals" },
61+
{ value: "group", label: "Column groups" },
62+
{ value: "econ", label: "Economics" },
63+
],
64+
itemComponent: (item) => {
65+
return h("div.item", { key: item.data.id }, [
66+
h("div.name", item.data.name),
67+
h("div.type", item.data.type),
68+
]);
69+
},
4270
};

yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,6 +2641,7 @@ __metadata:
26412641
"@blueprintjs/datetime2": "npm:^2.3.11"
26422642
"@blueprintjs/select": "npm:^5.3.10"
26432643
"@macrostrat/hyper": "npm:^3.0.6"
2644+
"@macrostrat/map-interface": "workspace:^"
26442645
"@types/react": "npm:^18.3.12"
26452646
"@types/react-dom": "npm:^18"
26462647
axios: "npm:^1.7.9"

0 commit comments

Comments
 (0)