@databiosphere/findable-ui 41.0.0 → 41.2.0

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 (41) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/lib/components/DataDictionary/components/Filters/components/ColumnFilters/columnFilters.js +11 -1
  4. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/barX/plot.d.ts +1 -1
  5. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/barX/plot.js +3 -4
  6. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/chart.js +22 -5
  7. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/chart.styles.d.ts +5 -0
  8. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/chart.styles.js +5 -0
  9. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/constants.d.ts +1 -0
  10. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/constants.js +1 -0
  11. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/hook.d.ts +3 -0
  12. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/hook.js +9 -0
  13. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/types.d.ts +4 -0
  14. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/types.js +1 -0
  15. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/utils.d.ts +20 -0
  16. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/utils.js +28 -0
  17. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UsePlotOptions/hook.d.ts +3 -0
  18. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UsePlotOptions/hook.js +15 -0
  19. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UsePlotOptions/types.d.ts +4 -0
  20. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UsePlotOptions/types.js +1 -0
  21. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/utils.d.ts +15 -0
  22. package/lib/components/Index/components/EntityView/components/views/ChartView/components/Chart/utils.js +25 -0
  23. package/lib/components/Table/components/TableFeatures/ColumnFilter/columnFilter.js +2 -1
  24. package/lib/hooks/useCategoryFilter.d.ts +8 -1
  25. package/lib/hooks/useCategoryFilter.js +1 -1
  26. package/lib/theme/common/components.js +10 -1
  27. package/package.json +1 -1
  28. package/src/components/DataDictionary/components/Filters/components/ColumnFilters/columnFilters.tsx +13 -2
  29. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/barX/plot.ts +2 -3
  30. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/chart.styles.ts +6 -0
  31. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/chart.tsx +30 -7
  32. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/constants.ts +1 -0
  33. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/hook.ts +18 -0
  34. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/types.ts +4 -0
  35. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UseBarCount/utils.ts +38 -0
  36. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UsePlotOptions/hook.ts +32 -0
  37. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/hooks/UsePlotOptions/types.ts +5 -0
  38. package/src/components/Index/components/EntityView/components/views/ChartView/components/Chart/utils.ts +35 -0
  39. package/src/components/Table/components/TableFeatures/ColumnFilter/columnFilter.tsx +2 -0
  40. package/src/hooks/useCategoryFilter.ts +1 -1
  41. package/src/theme/common/components.ts +10 -1
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "41.0.0"
2
+ ".": "41.2.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [41.2.0](https://github.com/DataBiosphere/findable-ui/compare/v41.1.0...v41.2.0) (2025-08-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * chart view - show more link for long tail charts ([#446](https://github.com/DataBiosphere/findable-ui/issues/446)) ([#606](https://github.com/DataBiosphere/findable-ui/issues/606)) ([fb035d8](https://github.com/DataBiosphere/findable-ui/commit/fb035d8bbb5ac47ef656ef7403ac271ffdb7e0af))
9
+
10
+ ## [41.1.0](https://github.com/DataBiosphere/findable-ui/compare/v41.0.0...v41.1.0) (2025-07-31)
11
+
12
+
13
+ ### Features
14
+
15
+ * update data dictionary column filter button size to large ([#603](https://github.com/DataBiosphere/findable-ui/issues/603)) ([#604](https://github.com/DataBiosphere/findable-ui/issues/604)) ([a1d7c76](https://github.com/DataBiosphere/findable-ui/commit/a1d7c76bad1386c7c56bbb4c5f65f9905addc71e))
16
+
3
17
  ## [41.0.0](https://github.com/DataBiosphere/findable-ui/compare/v40.0.0...v41.0.0) (2025-07-30)
4
18
 
5
19
 
@@ -1,7 +1,9 @@
1
1
  import { ButtonGroup, useMediaQuery } from "@mui/material";
2
2
  import React from "react";
3
+ import { BUTTON_PROPS } from "../../../../../../styles/common/mui/button";
3
4
  import { BUTTON_GROUP_PROPS } from "../../../../../common/ButtonGroup/constants";
4
5
  import { ColumnFiltersAdapter } from "../../../../../Filter/components/adapters/tanstack/ColumnFiltersAdapter/columnFiltersAdapter";
6
+ import { Button } from "../../../../../Filter/components/surfaces/drawer/components/Button/button";
5
7
  import { Drawer } from "../../../../../Filter/components/surfaces/drawer/Drawer/drawer";
6
8
  import { ColumnFilter } from "../../../../../Table/components/TableFeatures/ColumnFilter/columnFilter";
7
9
  export const ColumnFilters = ({ table, }) => {
@@ -12,6 +14,14 @@ export const ColumnFilters = ({ table, }) => {
12
14
  if (!enableColumnFilters)
13
15
  return null;
14
16
  if (isDrawer)
15
- return (React.createElement(ColumnFiltersAdapter, { table: table, renderSurface: (props) => React.createElement(Drawer, { ...props }) }));
17
+ return (React.createElement(ColumnFiltersAdapter, { table: table, renderSurface: (props) => React.createElement(Drawer, { Button: renderButton, ...props }) }));
16
18
  return (React.createElement(ButtonGroup, { ...BUTTON_GROUP_PROPS.SECONDARY_OUTLINED }, columnFilters.map((column) => (React.createElement(ColumnFilter, { key: column.id, column: column })))));
17
19
  };
20
+ /**
21
+ * Renders the drawer's open button with the specified size.
22
+ * @param props - Button props.
23
+ * @returns Button.
24
+ */
25
+ function renderButton(props) {
26
+ return React.createElement(Button, { size: BUTTON_PROPS.SIZE.LARGE, ...props });
27
+ }
@@ -1,3 +1,3 @@
1
1
  import { PlotOptions } from "@observablehq/plot";
2
2
  import { SelectCategoryValueView } from "../../../../../../../../../../common/entities";
3
- export declare function getPlotOptions(selectCategoryValueViews: SelectCategoryValueView[], width: number): PlotOptions;
3
+ export declare function getPlotOptions(selectCategoryValueViews: SelectCategoryValueView[], totalCount: number, width: number): PlotOptions;
@@ -2,10 +2,9 @@ import * as Plot from "@observablehq/plot";
2
2
  import { PALETTE } from "../../../../../../../../../../styles/common/constants/palette";
3
3
  import { formatCountSize } from "../../../../../../../../../../utils/formatCountSize";
4
4
  import { DATA_FIELD, MARGIN_LEFT, TEXT_PADDING } from "./constants";
5
- import { getCategoryTotalCount, getCategoryValueText, getCategoryValueTextFill, getColorRangeValue, getCountText, getCountTextFill, getPlotHeight, getTicks, getXDomain, getYPaddingInner, getYPaddingOuter, isAnyValueSelected, renderText, } from "./utils";
6
- export function getPlotOptions(selectCategoryValueViews, width) {
5
+ import { getCategoryValueText, getCategoryValueTextFill, getColorRangeValue, getCountText, getCountTextFill, getPlotHeight, getTicks, getXDomain, getYPaddingInner, getYPaddingOuter, isAnyValueSelected, renderText, } from "./utils";
6
+ export function getPlotOptions(selectCategoryValueViews, totalCount, width) {
7
7
  const isCategorySelected = isAnyValueSelected(selectCategoryValueViews);
8
- const totalCount = getCategoryTotalCount(selectCategoryValueViews);
9
8
  return {
10
9
  color: {
11
10
  domain: [false, true], // false = unselected, true = selected.
@@ -34,7 +33,6 @@ export function getPlotOptions(selectCategoryValueViews, width) {
34
33
  fill: DATA_FIELD.SELECTED,
35
34
  rx1: 0,
36
35
  rx2: 4,
37
- sort: { order: "descending", y: "x" }, // Sort by count (x-axis), descending.
38
36
  x: DATA_FIELD.COUNT,
39
37
  y: DATA_FIELD.LABEL,
40
38
  }),
@@ -79,6 +77,7 @@ export function getPlotOptions(selectCategoryValueViews, width) {
79
77
  line: false,
80
78
  },
81
79
  y: {
80
+ domain: selectCategoryValueViews.map((d) => d.label), // Preserve the order of the pre-sorted data.
82
81
  label: null,
83
82
  line: false,
84
83
  paddingInner: getYPaddingInner(),
@@ -1,7 +1,24 @@
1
- import React, { useMemo } from "react";
2
- import { getPlotOptions } from "./barX/plot";
3
- import { StyledBarX } from "./chart.styles";
1
+ import React, { Fragment } from "react";
2
+ import { BUTTON_PROPS } from "../../../../../../../../common/Button/constants";
3
+ import { StyledBarX, StyledButton } from "./chart.styles";
4
+ import { useBarCount } from "./hooks/UseBarCount/hook";
5
+ import { isChartExpandable } from "./hooks/UseBarCount/utils";
6
+ import { usePlotOptions } from "./hooks/UsePlotOptions/hook";
7
+ import { renderButtonText } from "./utils";
4
8
  export const Chart = ({ selectCategoryValueViews, testId, width, }) => {
5
- const options = useMemo(() => getPlotOptions(selectCategoryValueViews, width), [selectCategoryValueViews, width]);
6
- return React.createElement(StyledBarX, { options: options, testId: testId });
9
+ const { barCount, onToggleBarCount } = useBarCount(selectCategoryValueViews);
10
+ const { options } = usePlotOptions(selectCategoryValueViews, width, barCount);
11
+ return (React.createElement(Fragment, null,
12
+ React.createElement(StyledBarX, { options: options, testId: testId }),
13
+ isChartExpandable(selectCategoryValueViews) && (React.createElement(StyledButton, { ...BUTTON_PROPS.PRIMARY_TEXT, onClick: (e) => {
14
+ // Scroll, only if we are closing the chart (i.e. barCount is undefined).
15
+ const shouldScroll = barCount === undefined;
16
+ onToggleBarCount();
17
+ if (!shouldScroll)
18
+ return;
19
+ // Scroll the chart into view.
20
+ e.currentTarget
21
+ ?.closest("div")
22
+ ?.scrollIntoView({ behavior: "smooth" });
23
+ } }, renderButtonText(barCount, selectCategoryValueViews)))));
7
24
  };
@@ -1,3 +1,8 @@
1
1
  export declare const StyledBarX: import("@emotion/styled").StyledComponent<import("../../../../../../../../Plot/components/BarX/types").BarXProps & {
2
2
  theme?: import("@emotion/react").Theme;
3
3
  }, {}, {}>;
4
+ export declare const StyledButton: import("@emotion/styled").StyledComponent<import("@mui/material").ButtonOwnProps & Omit<import("@mui/material").ButtonBaseOwnProps, "classes"> & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
5
+ ref?: ((instance: HTMLButtonElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLButtonElement> | null | undefined;
6
+ }, "style" | "size" | "href" | "className" | "classes" | "children" | "color" | "sx" | "tabIndex" | "disabled" | "action" | "loading" | "centerRipple" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "onFocusVisible" | "TouchRippleProps" | "touchRippleRef" | "disableElevation" | "disableFocusRipple" | "endIcon" | "fullWidth" | "loadingIndicator" | "loadingPosition" | "startIcon" | "variant"> & {
7
+ theme?: import("@emotion/react").Theme;
8
+ }, {}, {}>;
@@ -1,4 +1,5 @@
1
1
  import styled from "@emotion/styled";
2
+ import { Button } from "@mui/material";
2
3
  import { mediaTabletDown } from "../../../../../../../../../styles/common/mixins/breakpoints";
3
4
  import { textBodySmall400 } from "../../../../../../../../../styles/common/mixins/fonts";
4
5
  import { BarX } from "../../../../../../../../Plot/components/BarX/barX";
@@ -27,3 +28,7 @@ export const StyledBarX = styled(BarX) `
27
28
  }
28
29
  }
29
30
  `;
31
+ export const StyledButton = styled(Button) `
32
+ align-self: flex-start;
33
+ text-transform: none;
34
+ `;
@@ -0,0 +1,3 @@
1
+ import { SelectCategoryValueView } from "../../../../../../../../../../../common/entities";
2
+ import { UseBarCount } from "./types";
3
+ export declare const useBarCount: (selectCategoryValueViews: SelectCategoryValueView[]) => UseBarCount;
@@ -0,0 +1,9 @@
1
+ import { useCallback, useState } from "react";
2
+ import { initBarCount, updateBarCount } from "./utils";
3
+ export const useBarCount = (selectCategoryValueViews) => {
4
+ const [barCount, setBarCount] = useState(initBarCount(selectCategoryValueViews));
5
+ const onToggleBarCount = useCallback(() => {
6
+ setBarCount(updateBarCount);
7
+ }, []);
8
+ return { barCount, onToggleBarCount };
9
+ };
@@ -0,0 +1,4 @@
1
+ export type UseBarCount = {
2
+ barCount: number | undefined;
3
+ onToggleBarCount: () => void;
4
+ };
@@ -0,0 +1,20 @@
1
+ import { SelectCategoryValueView } from "../../../../../../../../../../../common/entities";
2
+ /**
3
+ * Returns the initial bar count for the chart.
4
+ * @param selectCategoryValueViews - Category value views.
5
+ * @returns Initial bar count.
6
+ */
7
+ export declare function initBarCount(selectCategoryValueViews: SelectCategoryValueView[]): number | undefined;
8
+ /**
9
+ * Returns true if the chart is expandable.
10
+ * The chart is expandable if the number of category values is greater than the maximum bar count.
11
+ * @param selectCategoryValueViews - Category value views.
12
+ * @returns True if the chart is expandable.
13
+ */
14
+ export declare function isChartExpandable(selectCategoryValueViews: SelectCategoryValueView[]): boolean;
15
+ /**
16
+ * Updates the bar count for the chart.
17
+ * @param barCount - Current bar count.
18
+ * @returns Updated bar count.
19
+ */
20
+ export declare function updateBarCount(barCount: number | undefined): number | undefined;
@@ -0,0 +1,28 @@
1
+ import { MAX_BAR_COUNT } from "./constants";
2
+ /**
3
+ * Returns the initial bar count for the chart.
4
+ * @param selectCategoryValueViews - Category value views.
5
+ * @returns Initial bar count.
6
+ */
7
+ export function initBarCount(selectCategoryValueViews) {
8
+ return isChartExpandable(selectCategoryValueViews)
9
+ ? MAX_BAR_COUNT
10
+ : undefined;
11
+ }
12
+ /**
13
+ * Returns true if the chart is expandable.
14
+ * The chart is expandable if the number of category values is greater than the maximum bar count.
15
+ * @param selectCategoryValueViews - Category value views.
16
+ * @returns True if the chart is expandable.
17
+ */
18
+ export function isChartExpandable(selectCategoryValueViews) {
19
+ return selectCategoryValueViews.length > MAX_BAR_COUNT;
20
+ }
21
+ /**
22
+ * Updates the bar count for the chart.
23
+ * @param barCount - Current bar count.
24
+ * @returns Updated bar count.
25
+ */
26
+ export function updateBarCount(barCount) {
27
+ return barCount === undefined ? MAX_BAR_COUNT : undefined;
28
+ }
@@ -0,0 +1,3 @@
1
+ import { SelectCategoryValueView } from "../../../../../../../../../../../common/entities";
2
+ import { UsePlotOptions } from "./types";
3
+ export declare const usePlotOptions: (selectCategoryValueViews: SelectCategoryValueView[], width: number, barCount: number | undefined) => UsePlotOptions;
@@ -0,0 +1,15 @@
1
+ import { useMemo } from "react";
2
+ import { getPlotOptions } from "../../barX/plot";
3
+ import { getCategoryTotalCount } from "../../barX/utils";
4
+ import { sortByCountThenLabel } from "../../utils";
5
+ export const usePlotOptions = (selectCategoryValueViews, width, barCount) => {
6
+ // Organise the select category value views (sort and slice) for chart display.
7
+ const data = selectCategoryValueViews
8
+ // Sort the category values by count and label.
9
+ .sort(sortByCountThenLabel)
10
+ // Slice the category values to the number of bars to display.
11
+ .slice(0, barCount);
12
+ // Build the plot options.
13
+ const options = useMemo(() => getPlotOptions(data, getCategoryTotalCount(selectCategoryValueViews), width), [data, selectCategoryValueViews, width]);
14
+ return { options };
15
+ };
@@ -0,0 +1,4 @@
1
+ import { PlotOptions } from "@observablehq/plot";
2
+ export interface UsePlotOptions {
3
+ options: PlotOptions;
4
+ }
@@ -0,0 +1,15 @@
1
+ import { SelectCategoryValueView } from "../../../../../../../../../common/entities";
2
+ /**
3
+ * Renders the button text for the chart.
4
+ * @param maxBarCount - Maximum number of bars to display.
5
+ * @param selectCategoryValueViews - Category value views.
6
+ * @returns Button text.
7
+ */
8
+ export declare function renderButtonText(maxBarCount: number | undefined, selectCategoryValueViews: SelectCategoryValueView[]): string;
9
+ /**
10
+ * Sorts category value views by count in descending order, then label in ascending order.
11
+ * @param a - First category value view.
12
+ * @param b - Second category value view.
13
+ * @returns Sorted category value views.
14
+ */
15
+ export declare function sortByCountThenLabel(a: SelectCategoryValueView, b: SelectCategoryValueView): number;
@@ -0,0 +1,25 @@
1
+ import { sortCategoryValueViews } from "../../../../../../../../../hooks/useCategoryFilter";
2
+ /**
3
+ * Renders the button text for the chart.
4
+ * @param maxBarCount - Maximum number of bars to display.
5
+ * @param selectCategoryValueViews - Category value views.
6
+ * @returns Button text.
7
+ */
8
+ export function renderButtonText(maxBarCount, selectCategoryValueViews) {
9
+ if (!maxBarCount)
10
+ return "Show less";
11
+ // Calculate the number of additional bars to show.
12
+ const totalBars = selectCategoryValueViews.length;
13
+ const count = totalBars - maxBarCount;
14
+ return `Show ${count} additional results`;
15
+ }
16
+ /**
17
+ * Sorts category value views by count in descending order, then label in ascending order.
18
+ * @param a - First category value view.
19
+ * @param b - Second category value view.
20
+ * @returns Sorted category value views.
21
+ */
22
+ export function sortByCountThenLabel(a, b) {
23
+ const compare = b.count - a.count;
24
+ return compare === 0 ? sortCategoryValueViews(a, b) : compare;
25
+ }
@@ -1,5 +1,6 @@
1
1
  import { Grid, Typography } from "@mui/material";
2
2
  import React, { Fragment, useCallback } from "react";
3
+ import { BUTTON_PROPS } from "../../../../../styles/common/mui/button";
3
4
  import { SVG_ICON_PROPS } from "../../../../../styles/common/mui/svgIcon";
4
5
  import { TYPOGRAPHY_PROPS } from "../../../../../styles/common/mui/typography";
5
6
  import { DropDownIcon } from "../../../../common/Form/components/Select/components/DropDownIcon/dropDownIcon";
@@ -28,7 +29,7 @@ export const ColumnFilter = ({ Button = StyledButton, className, column, ...prop
28
29
  column.setFilterValue(updater(value));
29
30
  }, [column]);
30
31
  return (React.createElement(Fragment, null,
31
- React.createElement(Button, { disabled: sortedValues.length === 0, endIcon: React.createElement(DropDownIcon, { color: SVG_ICON_PROPS.COLOR.INK_LIGHT }), onClick: onOpen, open: open },
32
+ React.createElement(Button, { disabled: sortedValues.length === 0, endIcon: React.createElement(DropDownIcon, { color: SVG_ICON_PROPS.COLOR.INK_LIGHT }), onClick: onOpen, open: open, size: BUTTON_PROPS.SIZE.LARGE },
32
33
  React.createElement(Grid, { ...GRID_PROPS },
33
34
  getColumnHeader(column),
34
35
  React.createElement(FilterCountChip, { count: filterValue.length }))),
@@ -1,7 +1,7 @@
1
1
  import { CategoryConfig } from "../common/categories/config/types";
2
2
  import { Category } from "../common/categories/models/types";
3
3
  import { CategoryView, VIEW_KIND } from "../common/categories/views/types";
4
- import { CategoryKey, CategoryValueKey, ClearAll, Filters, SelectCategoryValue } from "../common/entities";
4
+ import { CategoryKey, CategoryValueKey, ClearAll, Filters, SelectCategoryValue, SelectCategoryValueView } from "../common/entities";
5
5
  /**
6
6
  * State backing filter functionality and calculations. Converted to view model for display.
7
7
  */
@@ -34,3 +34,10 @@ export declare function buildNextFilterState(filterState: FilterState, categoryK
34
34
  * @returns original select category value.
35
35
  */
36
36
  export declare function getSelectCategoryValue(selectCategoryValue: SelectCategoryValue): SelectCategoryValue;
37
+ /**
38
+ * Sort category value views by key, ascending.
39
+ * @param cvv0 - First category value view to compare.
40
+ * @param cvv1 - Second category value view to compare.
41
+ * @returns Number indicating sort precedence of cv0 vs cv1.
42
+ */
43
+ export declare function sortCategoryValueViews(cvv0: SelectCategoryValueView, cvv1: SelectCategoryValueView): number;
@@ -162,7 +162,7 @@ function isCategoryAcceptListed(category, categoryConfigs) {
162
162
  * @param cvv1 - Second category value view to compare.
163
163
  * @returns Number indicating sort precedence of cv0 vs cv1.
164
164
  */
165
- function sortCategoryValueViews(cvv0, cvv1) {
165
+ export function sortCategoryValueViews(cvv0, cvv1) {
166
166
  return !cvv0.label
167
167
  ? 1
168
168
  : !cvv1.label
@@ -241,7 +241,16 @@ export const MuiButton = (theme) => {
241
241
  variants: [
242
242
  {
243
243
  props: { size: BUTTON_PROPS.SIZE.LARGE },
244
- style: { padding: "10px 16px" },
244
+ style: {
245
+ padding: "10px 16px",
246
+ // eslint-disable-next-line sort-keys -- disabling key order for readability
247
+ ".MuiButton-iconSizeLarge": {
248
+ // eslint-disable-next-line sort-keys -- disabling key order for readability
249
+ ".MuiSvgIcon-root": {
250
+ fontSize: "20px",
251
+ },
252
+ },
253
+ },
245
254
  },
246
255
  {
247
256
  props: { size: BUTTON_PROPS.SIZE.MEDIUM },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "41.0.0",
3
+ "version": "41.2.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -1,9 +1,11 @@
1
1
  import { ButtonGroup, Theme, useMediaQuery } from "@mui/material";
2
2
  import { RowData } from "@tanstack/react-table";
3
- import React from "react";
3
+ import React, { ComponentProps } from "react";
4
4
  import { Attribute } from "../../../../../../common/entities";
5
+ import { BUTTON_PROPS } from "../../../../../../styles/common/mui/button";
5
6
  import { BUTTON_GROUP_PROPS } from "../../../../../common/ButtonGroup/constants";
6
7
  import { ColumnFiltersAdapter } from "../../../../../Filter/components/adapters/tanstack/ColumnFiltersAdapter/columnFiltersAdapter";
8
+ import { Button } from "../../../../../Filter/components/surfaces/drawer/components/Button/button";
7
9
  import { Drawer } from "../../../../../Filter/components/surfaces/drawer/Drawer/drawer";
8
10
  import { ColumnFilter } from "../../../../../Table/components/TableFeatures/ColumnFilter/columnFilter";
9
11
  import { ColumnFiltersProps } from "./types";
@@ -22,7 +24,7 @@ export const ColumnFilters = <T extends RowData = Attribute>({
22
24
  return (
23
25
  <ColumnFiltersAdapter
24
26
  table={table}
25
- renderSurface={(props) => <Drawer {...props} />}
27
+ renderSurface={(props) => <Drawer Button={renderButton} {...props} />}
26
28
  />
27
29
  );
28
30
 
@@ -34,3 +36,12 @@ export const ColumnFilters = <T extends RowData = Attribute>({
34
36
  </ButtonGroup>
35
37
  );
36
38
  };
39
+
40
+ /**
41
+ * Renders the drawer's open button with the specified size.
42
+ * @param props - Button props.
43
+ * @returns Button.
44
+ */
45
+ function renderButton(props: ComponentProps<typeof Button>): JSX.Element {
46
+ return <Button size={BUTTON_PROPS.SIZE.LARGE} {...props} />;
47
+ }
@@ -5,7 +5,6 @@ import { PALETTE } from "../../../../../../../../../../styles/common/constants/p
5
5
  import { formatCountSize } from "../../../../../../../../../../utils/formatCountSize";
6
6
  import { DATA_FIELD, MARGIN_LEFT, TEXT_PADDING } from "./constants";
7
7
  import {
8
- getCategoryTotalCount,
9
8
  getCategoryValueText,
10
9
  getCategoryValueTextFill,
11
10
  getColorRangeValue,
@@ -22,10 +21,10 @@ import {
22
21
 
23
22
  export function getPlotOptions(
24
23
  selectCategoryValueViews: SelectCategoryValueView[],
24
+ totalCount: number,
25
25
  width: number
26
26
  ): PlotOptions {
27
27
  const isCategorySelected = isAnyValueSelected(selectCategoryValueViews);
28
- const totalCount = getCategoryTotalCount(selectCategoryValueViews);
29
28
  return {
30
29
  color: {
31
30
  domain: [false, true], // false = unselected, true = selected.
@@ -54,7 +53,6 @@ export function getPlotOptions(
54
53
  fill: DATA_FIELD.SELECTED,
55
54
  rx1: 0,
56
55
  rx2: 4,
57
- sort: { order: "descending", y: "x" }, // Sort by count (x-axis), descending.
58
56
  x: DATA_FIELD.COUNT,
59
57
  y: DATA_FIELD.LABEL,
60
58
  }),
@@ -99,6 +97,7 @@ export function getPlotOptions(
99
97
  line: false,
100
98
  },
101
99
  y: {
100
+ domain: selectCategoryValueViews.map((d) => d.label), // Preserve the order of the pre-sorted data.
102
101
  label: null,
103
102
  line: false,
104
103
  paddingInner: getYPaddingInner(),
@@ -1,4 +1,5 @@
1
1
  import styled from "@emotion/styled";
2
+ import { Button } from "@mui/material";
2
3
  import { mediaTabletDown } from "../../../../../../../../../styles/common/mixins/breakpoints";
3
4
  import { textBodySmall400 } from "../../../../../../../../../styles/common/mixins/fonts";
4
5
  import { BarX } from "../../../../../../../../Plot/components/BarX/barX";
@@ -28,3 +29,8 @@ export const StyledBarX = styled(BarX)`
28
29
  }
29
30
  }
30
31
  `;
32
+
33
+ export const StyledButton = styled(Button)`
34
+ align-self: flex-start;
35
+ text-transform: none;
36
+ `;
@@ -1,16 +1,39 @@
1
- import React, { useMemo } from "react";
2
- import { getPlotOptions } from "./barX/plot";
3
- import { StyledBarX } from "./chart.styles";
1
+ import React, { Fragment } from "react";
2
+ import { BUTTON_PROPS } from "../../../../../../../../common/Button/constants";
3
+ import { StyledBarX, StyledButton } from "./chart.styles";
4
+ import { useBarCount } from "./hooks/UseBarCount/hook";
5
+ import { isChartExpandable } from "./hooks/UseBarCount/utils";
6
+ import { usePlotOptions } from "./hooks/UsePlotOptions/hook";
4
7
  import { ChartProps } from "./types";
8
+ import { renderButtonText } from "./utils";
5
9
 
6
10
  export const Chart = ({
7
11
  selectCategoryValueViews,
8
12
  testId,
9
13
  width,
10
14
  }: ChartProps): JSX.Element => {
11
- const options = useMemo(
12
- () => getPlotOptions(selectCategoryValueViews, width),
13
- [selectCategoryValueViews, width]
15
+ const { barCount, onToggleBarCount } = useBarCount(selectCategoryValueViews);
16
+ const { options } = usePlotOptions(selectCategoryValueViews, width, barCount);
17
+ return (
18
+ <Fragment>
19
+ <StyledBarX options={options} testId={testId} />
20
+ {isChartExpandable(selectCategoryValueViews) && (
21
+ <StyledButton
22
+ {...BUTTON_PROPS.PRIMARY_TEXT}
23
+ onClick={(e) => {
24
+ // Scroll, only if we are closing the chart (i.e. barCount is undefined).
25
+ const shouldScroll = barCount === undefined;
26
+ onToggleBarCount();
27
+ if (!shouldScroll) return;
28
+ // Scroll the chart into view.
29
+ e.currentTarget
30
+ ?.closest("div")
31
+ ?.scrollIntoView({ behavior: "smooth" });
32
+ }}
33
+ >
34
+ {renderButtonText(barCount, selectCategoryValueViews)}
35
+ </StyledButton>
36
+ )}
37
+ </Fragment>
14
38
  );
15
- return <StyledBarX options={options} testId={testId} />;
16
39
  };
@@ -0,0 +1,18 @@
1
+ import { useCallback, useState } from "react";
2
+ import { SelectCategoryValueView } from "../../../../../../../../../../../common/entities";
3
+ import { UseBarCount } from "./types";
4
+ import { initBarCount, updateBarCount } from "./utils";
5
+
6
+ export const useBarCount = (
7
+ selectCategoryValueViews: SelectCategoryValueView[]
8
+ ): UseBarCount => {
9
+ const [barCount, setBarCount] = useState<number | undefined>(
10
+ initBarCount(selectCategoryValueViews)
11
+ );
12
+
13
+ const onToggleBarCount = useCallback(() => {
14
+ setBarCount(updateBarCount);
15
+ }, []);
16
+
17
+ return { barCount, onToggleBarCount };
18
+ };
@@ -0,0 +1,4 @@
1
+ export type UseBarCount = {
2
+ barCount: number | undefined;
3
+ onToggleBarCount: () => void;
4
+ };
@@ -0,0 +1,38 @@
1
+ import { SelectCategoryValueView } from "../../../../../../../../../../../common/entities";
2
+ import { MAX_BAR_COUNT } from "./constants";
3
+
4
+ /**
5
+ * Returns the initial bar count for the chart.
6
+ * @param selectCategoryValueViews - Category value views.
7
+ * @returns Initial bar count.
8
+ */
9
+ export function initBarCount(
10
+ selectCategoryValueViews: SelectCategoryValueView[]
11
+ ): number | undefined {
12
+ return isChartExpandable(selectCategoryValueViews)
13
+ ? MAX_BAR_COUNT
14
+ : undefined;
15
+ }
16
+
17
+ /**
18
+ * Returns true if the chart is expandable.
19
+ * The chart is expandable if the number of category values is greater than the maximum bar count.
20
+ * @param selectCategoryValueViews - Category value views.
21
+ * @returns True if the chart is expandable.
22
+ */
23
+ export function isChartExpandable(
24
+ selectCategoryValueViews: SelectCategoryValueView[]
25
+ ): boolean {
26
+ return selectCategoryValueViews.length > MAX_BAR_COUNT;
27
+ }
28
+
29
+ /**
30
+ * Updates the bar count for the chart.
31
+ * @param barCount - Current bar count.
32
+ * @returns Updated bar count.
33
+ */
34
+ export function updateBarCount(
35
+ barCount: number | undefined
36
+ ): number | undefined {
37
+ return barCount === undefined ? MAX_BAR_COUNT : undefined;
38
+ }
@@ -0,0 +1,32 @@
1
+ import { useMemo } from "react";
2
+ import { SelectCategoryValueView } from "../../../../../../../../../../../common/entities";
3
+ import { getPlotOptions } from "../../barX/plot";
4
+ import { getCategoryTotalCount } from "../../barX/utils";
5
+ import { sortByCountThenLabel } from "../../utils";
6
+ import { UsePlotOptions } from "./types";
7
+
8
+ export const usePlotOptions = (
9
+ selectCategoryValueViews: SelectCategoryValueView[],
10
+ width: number,
11
+ barCount: number | undefined
12
+ ): UsePlotOptions => {
13
+ // Organise the select category value views (sort and slice) for chart display.
14
+ const data = selectCategoryValueViews
15
+ // Sort the category values by count and label.
16
+ .sort(sortByCountThenLabel)
17
+ // Slice the category values to the number of bars to display.
18
+ .slice(0, barCount);
19
+
20
+ // Build the plot options.
21
+ const options = useMemo(
22
+ () =>
23
+ getPlotOptions(
24
+ data,
25
+ getCategoryTotalCount(selectCategoryValueViews),
26
+ width
27
+ ),
28
+ [data, selectCategoryValueViews, width]
29
+ );
30
+
31
+ return { options };
32
+ };
@@ -0,0 +1,5 @@
1
+ import { PlotOptions } from "@observablehq/plot";
2
+
3
+ export interface UsePlotOptions {
4
+ options: PlotOptions;
5
+ }
@@ -0,0 +1,35 @@
1
+ import { SelectCategoryValueView } from "../../../../../../../../../common/entities";
2
+ import { sortCategoryValueViews } from "../../../../../../../../../hooks/useCategoryFilter";
3
+
4
+ /**
5
+ * Renders the button text for the chart.
6
+ * @param maxBarCount - Maximum number of bars to display.
7
+ * @param selectCategoryValueViews - Category value views.
8
+ * @returns Button text.
9
+ */
10
+ export function renderButtonText(
11
+ maxBarCount: number | undefined,
12
+ selectCategoryValueViews: SelectCategoryValueView[]
13
+ ): string {
14
+ if (!maxBarCount) return "Show less";
15
+
16
+ // Calculate the number of additional bars to show.
17
+ const totalBars = selectCategoryValueViews.length;
18
+ const count = totalBars - maxBarCount;
19
+
20
+ return `Show ${count} additional results`;
21
+ }
22
+
23
+ /**
24
+ * Sorts category value views by count in descending order, then label in ascending order.
25
+ * @param a - First category value view.
26
+ * @param b - Second category value view.
27
+ * @returns Sorted category value views.
28
+ */
29
+ export function sortByCountThenLabel(
30
+ a: SelectCategoryValueView,
31
+ b: SelectCategoryValueView
32
+ ): number {
33
+ const compare = b.count - a.count;
34
+ return compare === 0 ? sortCategoryValueViews(a, b) : compare;
35
+ }
@@ -1,6 +1,7 @@
1
1
  import { Grid, Typography } from "@mui/material";
2
2
  import { RowData } from "@tanstack/react-table";
3
3
  import React, { Fragment, useCallback } from "react";
4
+ import { BUTTON_PROPS } from "../../../../../styles/common/mui/button";
4
5
  import { SVG_ICON_PROPS } from "../../../../../styles/common/mui/svgIcon";
5
6
  import { TYPOGRAPHY_PROPS } from "../../../../../styles/common/mui/typography";
6
7
  import { DropDownIcon } from "../../../../common/Form/components/Select/components/DropDownIcon/dropDownIcon";
@@ -51,6 +52,7 @@ export const ColumnFilter = <T extends RowData>({
51
52
  endIcon={<DropDownIcon color={SVG_ICON_PROPS.COLOR.INK_LIGHT} />}
52
53
  onClick={onOpen}
53
54
  open={open}
55
+ size={BUTTON_PROPS.SIZE.LARGE}
54
56
  >
55
57
  <Grid {...GRID_PROPS}>
56
58
  {getColumnHeader(column)}
@@ -278,7 +278,7 @@ function isCategoryAcceptListed(
278
278
  * @param cvv1 - Second category value view to compare.
279
279
  * @returns Number indicating sort precedence of cv0 vs cv1.
280
280
  */
281
- function sortCategoryValueViews(
281
+ export function sortCategoryValueViews(
282
282
  cvv0: SelectCategoryValueView,
283
283
  cvv1: SelectCategoryValueView
284
284
  ): number {
@@ -258,7 +258,16 @@ export const MuiButton = (theme: Theme): Components["MuiButton"] => {
258
258
  variants: [
259
259
  {
260
260
  props: { size: BUTTON_PROPS.SIZE.LARGE },
261
- style: { padding: "10px 16px" },
261
+ style: {
262
+ padding: "10px 16px",
263
+ // eslint-disable-next-line sort-keys -- disabling key order for readability
264
+ ".MuiButton-iconSizeLarge": {
265
+ // eslint-disable-next-line sort-keys -- disabling key order for readability
266
+ ".MuiSvgIcon-root": {
267
+ fontSize: "20px",
268
+ },
269
+ },
270
+ },
262
271
  },
263
272
  {
264
273
  props: { size: BUTTON_PROPS.SIZE.MEDIUM },