@databiosphere/findable-ui 38.3.0 → 39.1.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 (74) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/lib/components/DataDictionary/components/Entities/entities.d.ts +1 -1
  4. package/lib/components/DataDictionary/components/Entities/entities.js +2 -2
  5. package/lib/components/DataDictionary/components/Entities/types.d.ts +0 -2
  6. package/lib/components/DataDictionary/components/Entity/entity.d.ts +1 -1
  7. package/lib/components/DataDictionary/components/Entity/entity.js +3 -3
  8. package/lib/components/DataDictionary/components/Entity/entity.styles.js +0 -2
  9. package/lib/components/DataDictionary/components/Entity/types.d.ts +0 -2
  10. package/lib/components/DataDictionary/components/Table/table.js +6 -8
  11. package/lib/components/DataDictionary/components/Table/table.styles.d.ts +7 -0
  12. package/lib/components/DataDictionary/components/Table/table.styles.js +8 -0
  13. package/lib/components/DataDictionary/dataDictionary.js +6 -1
  14. package/lib/components/Detail/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.js +1 -1
  15. package/lib/components/Detail/components/Table/components/TableRows/tableRows.js +1 -1
  16. package/lib/components/Index/index.styles.js +3 -1
  17. package/lib/components/MarkdownRenderer/markdownRenderer.d.ts +1 -1
  18. package/lib/components/MarkdownRenderer/markdownRenderer.js +6 -3
  19. package/lib/components/MarkdownRenderer/rehypeHighlight.d.ts +10 -0
  20. package/lib/components/MarkdownRenderer/rehypeHighlight.js +49 -0
  21. package/lib/components/MarkdownRenderer/stories/args.d.ts +3 -0
  22. package/lib/components/MarkdownRenderer/stories/args.js +4 -0
  23. package/lib/components/MarkdownRenderer/stories/markdownRenderer.stories.d.ts +6 -0
  24. package/lib/components/MarkdownRenderer/stories/markdownRenderer.stories.js +22 -0
  25. package/lib/components/MarkdownRenderer/types.d.ts +1 -0
  26. package/lib/components/Table/columnDef/globalFilter/utils.js +5 -5
  27. package/lib/components/Table/components/TableCell/components/MarkdownCell/markdownCell.d.ts +2 -3
  28. package/lib/components/Table/components/TableCell/components/MarkdownCell/markdownCell.js +10 -5
  29. package/lib/components/Table/components/TableCell/components/MarkdownCell/stories/args.js +8 -6
  30. package/lib/components/Table/components/TableCell/components/RankedCell/rankedCell.d.ts +2 -2
  31. package/lib/components/Table/components/TableCell/components/RankedCell/rankedCell.js +12 -4
  32. package/lib/components/Table/components/TableCell/components/RankedCell/utils.d.ts +0 -9
  33. package/lib/components/Table/components/TableCell/components/RankedCell/utils.js +0 -27
  34. package/lib/components/common/AnchorLink/anchorLink.d.ts +3 -1
  35. package/lib/components/common/AnchorLink/anchorLink.js +2 -2
  36. package/lib/components/common/AnchorLink/anchorLink.styles.js +2 -2
  37. package/lib/hooks/useHtmlStyle/constants.d.ts +2 -0
  38. package/lib/hooks/useHtmlStyle/constants.js +3 -0
  39. package/lib/hooks/useHtmlStyle/hook.d.ts +2 -0
  40. package/lib/hooks/useHtmlStyle/hook.js +18 -0
  41. package/lib/hooks/useHtmlStyle/types.d.ts +2 -0
  42. package/package.json +1 -1
  43. package/src/components/DataDictionary/components/Entities/entities.tsx +1 -2
  44. package/src/components/DataDictionary/components/Entities/types.ts +0 -2
  45. package/src/components/DataDictionary/components/Entity/entity.styles.ts +1 -4
  46. package/src/components/DataDictionary/components/Entity/entity.tsx +9 -9
  47. package/src/components/DataDictionary/components/Entity/types.ts +0 -2
  48. package/src/components/DataDictionary/components/Table/table.styles.ts +9 -0
  49. package/src/components/DataDictionary/components/Table/table.tsx +17 -20
  50. package/src/components/DataDictionary/dataDictionary.tsx +7 -1
  51. package/src/components/Detail/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.tsx +5 -1
  52. package/src/components/Detail/components/Table/components/TableRows/tableRows.tsx +1 -0
  53. package/src/components/Index/index.styles.ts +3 -1
  54. package/src/components/MarkdownRenderer/markdownRenderer.tsx +12 -2
  55. package/src/components/MarkdownRenderer/rehypeHighlight.ts +54 -0
  56. package/src/components/MarkdownRenderer/stories/args.ts +8 -0
  57. package/src/components/MarkdownRenderer/stories/markdownRenderer.stories.tsx +33 -0
  58. package/src/components/MarkdownRenderer/types.ts +1 -0
  59. package/src/components/Table/columnDef/globalFilter/utils.ts +5 -5
  60. package/src/components/Table/components/TableCell/components/MarkdownCell/markdownCell.tsx +17 -7
  61. package/src/components/Table/components/TableCell/components/MarkdownCell/stories/args.ts +14 -13
  62. package/src/components/Table/components/TableCell/components/RankedCell/rankedCell.tsx +21 -4
  63. package/src/components/Table/components/TableCell/components/RankedCell/utils.ts +0 -37
  64. package/src/components/common/AnchorLink/anchorLink.styles.ts +2 -2
  65. package/src/components/common/AnchorLink/anchorLink.tsx +4 -1
  66. package/src/hooks/useHtmlStyle/constants.ts +5 -0
  67. package/src/hooks/useHtmlStyle/hook.ts +23 -0
  68. package/src/hooks/useHtmlStyle/types.ts +3 -0
  69. package/lib/components/Table/components/TableCell/components/MarkdownCell/stories/types.d.ts +0 -3
  70. package/lib/components/Table/components/TableCell/components/MarkdownCell/types.d.ts +0 -3
  71. package/lib/components/Table/components/TableCell/components/MarkdownCell/types.js +0 -1
  72. package/src/components/Table/components/TableCell/components/MarkdownCell/stories/types.ts +0 -4
  73. package/src/components/Table/components/TableCell/components/MarkdownCell/types.ts +0 -3
  74. /package/lib/{components/Table/components/TableCell/components/MarkdownCell/stories → hooks/useHtmlStyle}/types.js +0 -0
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "38.3.0"
2
+ ".": "39.1.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [39.1.0](https://github.com/DataBiosphere/findable-ui/compare/v39.0.0...v39.1.0) (2025-07-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * add row ids to data dictionary table for anchor link scrolling ([#576](https://github.com/DataBiosphere/findable-ui/issues/576)) ([#577](https://github.com/DataBiosphere/findable-ui/issues/577)) ([bd7d05b](https://github.com/DataBiosphere/findable-ui/commit/bd7d05b451a7d9190b92fb682d892600cde17ba2))
9
+
10
+ ## [39.0.0](https://github.com/DataBiosphere/findable-ui/compare/v38.3.0...v39.0.0) (2025-07-23)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * fix global search when a marked value is inside another HTML tag ([#539](https://github.com/DataBiosphere/findable-ui/issues/539)) (#573)
16
+
17
+ ### Features
18
+
19
+ * fix global search when a marked value is inside another HTML tag ([#539](https://github.com/DataBiosphere/findable-ui/issues/539)) ([#573](https://github.com/DataBiosphere/findable-ui/issues/573)) ([7b58c24](https://github.com/DataBiosphere/findable-ui/commit/7b58c2429ba947e752358a012a6a7d545f0ffc28))
20
+
3
21
  ## [38.3.0](https://github.com/DataBiosphere/findable-ui/compare/v38.2.0...v38.3.0) (2025-07-22)
4
22
 
5
23
 
@@ -1,4 +1,4 @@
1
1
  import { RowData } from "@tanstack/react-table";
2
2
  import { Attribute } from "../../../../common/entities";
3
3
  import { ClassesProps } from "./types";
4
- export declare const Entities: <T extends RowData = Attribute>({ spacing, table, }: ClassesProps<T>) => JSX.Element;
4
+ export declare const Entities: <T extends RowData = Attribute>({ table, }: ClassesProps<T>) => JSX.Element;
@@ -3,10 +3,10 @@ import React from "react";
3
3
  import { NoResults } from "../../../NoResults/noResults";
4
4
  import { Entity } from "../Entity/entity";
5
5
  import { GRID_PROPS, NO_RESULTS_PROPS } from "./constants";
6
- export const Entities = ({ spacing, table, }) => {
6
+ export const Entities = ({ table, }) => {
7
7
  const { getGroupedRowModel } = table;
8
8
  const { rows } = getGroupedRowModel();
9
9
  if (rows.length === 0)
10
10
  return React.createElement(NoResults, { ...NO_RESULTS_PROPS });
11
- return (React.createElement(Grid, { ...GRID_PROPS }, rows.map((row) => (React.createElement(Entity, { key: row.id, row: row, spacing: spacing, table: table })))));
11
+ return (React.createElement(Grid, { ...GRID_PROPS }, rows.map((row) => (React.createElement(Entity, { key: row.id, row: row, table: table })))));
12
12
  };
@@ -1,7 +1,5 @@
1
1
  import { RowData, Table } from "@tanstack/react-table";
2
2
  import { Attribute } from "../../../../common/entities";
3
- import { LayoutSpacing } from "../../../../hooks/UseLayoutSpacing/types";
4
3
  export interface ClassesProps<T extends RowData = Attribute> {
5
- spacing?: LayoutSpacing;
6
4
  table: Table<T>;
7
5
  }
@@ -1,4 +1,4 @@
1
1
  import { RowData } from "@tanstack/react-table";
2
2
  import { Attribute } from "../../../../common/entities";
3
3
  import { EntityProps } from "./types";
4
- export declare const Entity: <T extends RowData = Attribute>({ row, spacing, table, }: EntityProps<T>) => JSX.Element | null;
4
+ export declare const Entity: <T extends RowData = Attribute>({ row, table, }: EntityProps<T>) => JSX.Element | null;
@@ -6,7 +6,7 @@ import { Table } from "../Table/table";
6
6
  import { GRID_PROPS } from "./constants";
7
7
  import { StyledTypography } from "./entity.styles";
8
8
  import { getClassMeta } from "./utils";
9
- export const Entity = ({ row, spacing, table, }) => {
9
+ export const Entity = ({ row, table, }) => {
10
10
  // Get class key from row.
11
11
  const classKey = row.getValue("classKey");
12
12
  // Get class metadata from table options.
@@ -16,10 +16,10 @@ export const Entity = ({ row, spacing, table, }) => {
16
16
  return null;
17
17
  return (React.createElement(Grid, { ...GRID_PROPS, rowGap: 4 },
18
18
  React.createElement(Grid, { ...GRID_PROPS, rowGap: 1 },
19
- React.createElement(StyledTypography, { component: "h3", id: classKey, variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_HEADING_SMALL, ...spacing },
19
+ React.createElement(StyledTypography, { component: "h3", id: classKey, variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_HEADING_SMALL },
20
20
  cls.title,
21
21
  " ",
22
22
  React.createElement(AnchorLink, { anchorLink: classKey })),
23
- React.createElement(Typography, { color: TYPOGRAPHY_PROPS.COLOR.INK_LIGHT, component: "div", variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_400_2_LINES }, cls.description)),
23
+ cls.description && (React.createElement(Typography, { color: TYPOGRAPHY_PROPS.COLOR.INK_LIGHT, component: "div", variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_400_2_LINES }, cls.description))),
24
24
  React.createElement(Table, { row: row, table: table })));
25
25
  };
@@ -1,8 +1,6 @@
1
1
  import styled from "@emotion/styled";
2
2
  import { Typography } from "@mui/material";
3
3
  export const StyledTypography = styled(Typography) `
4
- scroll-margin-top: ${({ top = 0 }) => top}px;
5
-
6
4
  &:hover a {
7
5
  opacity: 1;
8
6
  }
@@ -1,8 +1,6 @@
1
1
  import { Row, RowData, Table } from "@tanstack/react-table";
2
2
  import { Attribute } from "../../../../common/entities";
3
- import { LayoutSpacing } from "../../../../hooks/UseLayoutSpacing/types";
4
3
  export interface EntityProps<T extends RowData = Attribute> {
5
4
  row: Row<T>;
6
- spacing?: LayoutSpacing;
7
5
  table: Table<T>;
8
6
  }
@@ -5,13 +5,11 @@ import { ROW_DIRECTION } from "../../../Table/common/entities";
5
5
  import { TableHead } from "../../../Table/components/TableHead/tableHead";
6
6
  import { GridTable } from "../../../Table/table.styles";
7
7
  import { getColumnTrackSizing } from "../../../TableCreator/options/columnTrackSizing/utils";
8
- import { RoundedPaper } from "../../../common/Paper/components/RoundedPaper/roundedPaper";
9
- import { GridPaper } from "../../../common/Paper/paper.styles";
8
+ import { StyledRoundedPaper } from "./table.styles";
10
9
  export const Table = ({ row, table, }) => {
11
- return (React.createElement(RoundedPaper, { elevation: 0 },
12
- React.createElement(GridPaper, null,
13
- React.createElement(TableContainer, null,
14
- React.createElement(GridTable, { gridTemplateColumns: getColumnTrackSizing(table.getVisibleFlatColumns()) },
15
- React.createElement(TableHead, { tableInstance: table }),
16
- React.createElement(TableBody, { rowDirection: ROW_DIRECTION.DEFAULT, rows: row.getLeafRows(), tableInstance: table }))))));
10
+ return (React.createElement(StyledRoundedPaper, { elevation: 0 },
11
+ React.createElement(TableContainer, null,
12
+ React.createElement(GridTable, { gridTemplateColumns: getColumnTrackSizing(table.getVisibleFlatColumns()) },
13
+ React.createElement(TableHead, { tableInstance: table }),
14
+ React.createElement(TableBody, { rowDirection: ROW_DIRECTION.DEFAULT, rows: row.getLeafRows(), tableInstance: table })))));
17
15
  };
@@ -0,0 +1,7 @@
1
+ export declare const StyledRoundedPaper: import("@emotion/styled").StyledComponent<import("../../../types").BaseComponentProps & import("@mui/material").PaperOwnProps & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
2
+ ref?: ((instance: HTMLDivElement | 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<HTMLDivElement> | null | undefined;
3
+ }, "style" | "className" | "classes" | "children" | "sx" | "elevation" | "square" | "variant"> & {
4
+ component?: React.ElementType;
5
+ } & {
6
+ theme?: import("@emotion/react").Theme;
7
+ }, {}, {}>;
@@ -0,0 +1,8 @@
1
+ import styled from "@emotion/styled";
2
+ import { PALETTE } from "../../../../styles/common/constants/palette";
3
+ import { RoundedPaper } from "../../../common/Paper/components/RoundedPaper/roundedPaper";
4
+ export const StyledRoundedPaper = styled(RoundedPaper) `
5
+ background-color: ${PALETTE.SMOKE_MAIN};
6
+ display: grid;
7
+ gap: 1px;
8
+ `;
@@ -1,5 +1,7 @@
1
1
  import { Fade } from "@mui/material";
2
2
  import React from "react";
3
+ import { PROPERTY } from "../../hooks/useHtmlStyle/constants";
4
+ import { useHtmlStyle } from "../../hooks/useHtmlStyle/hook";
3
5
  import { useLayoutSpacing } from "../../hooks/UseLayoutSpacing/hook";
4
6
  import { Description } from "./components/Description/description";
5
7
  import { Entities } from "./components/Entities/entities";
@@ -30,6 +32,9 @@ export const DataDictionary = ({ className, dictionary, EntitiesLayout = Default
30
32
  const table = useTable(dictionary, classes, tableOptions);
31
33
  // Dictionary outline.
32
34
  const outline = buildClassesOutline(table);
35
+ // Update scroll-padding-top on the HTML element.
36
+ // Scroll-snaps table rows to below the sticky filters.
37
+ useHtmlStyle(PROPERTY.SCROLL_PADDING_TOP, `${dimensions.height}px`);
33
38
  return (React.createElement(Fade, { in: spacing.top > 0 },
34
39
  React.createElement(View, { className: className },
35
40
  React.createElement(TitleLayout, { ...spacing },
@@ -42,5 +47,5 @@ export const DataDictionary = ({ className, dictionary, EntitiesLayout = Default
42
47
  React.createElement(Fade, { in: dimensions.height > 0 },
43
48
  React.createElement(EntitiesLayout, { spacing: entitiesSpacing },
44
49
  React.createElement(Description, { description: description }),
45
- React.createElement(Entities, { spacing: entitiesSpacing, table: table }))))));
50
+ React.createElement(Entities, { table: table }))))));
46
51
  };
@@ -10,7 +10,7 @@ export const CollapsableRows = ({ rows: leafOrSubRows, tableInstance, }) => {
10
10
  return (React.createElement(Fragment, null, (leafOrSubRows || rows).map((row) => {
11
11
  if (row.depth > 0)
12
12
  return null; // Hide sub rows.
13
- return (React.createElement(StyledTableRow, { key: row.id, isPreview: row.getIsPreview() },
13
+ return (React.createElement(StyledTableRow, { key: row.id, id: row.id, isPreview: row.getIsPreview() },
14
14
  React.createElement(CollapsableCell, { isDisabled: isCollapsableRowDisabled(tableInstance), row: row })));
15
15
  })));
16
16
  };
@@ -10,7 +10,7 @@ export const TableRows = ({ rows: leafOrSubRows, tableInstance, tableView, }) =>
10
10
  const { tableCell } = tableView || {};
11
11
  const { size: tableCellSize = "medium" } = tableCell || {};
12
12
  return (React.createElement(Fragment, null, (leafOrSubRows || rows).map((row) => {
13
- return (React.createElement(StyledTableRow, { key: row.id, canExpand: row.getCanExpand(), isExpanded: row.getIsExpanded(), isGrouped: row.getIsGrouped(), isPreview: row.getIsPreview(), onClick: () => handleToggleExpanded(row) }, row.getVisibleCells().map((cell) => {
13
+ return (React.createElement(StyledTableRow, { key: row.id, id: row.id, canExpand: row.getCanExpand(), isExpanded: row.getIsExpanded(), isGrouped: row.getIsGrouped(), isPreview: row.getIsPreview(), onClick: () => handleToggleExpanded(row) }, row.getVisibleCells().map((cell) => {
14
14
  if (cell.getIsAggregated())
15
15
  return null; // Display of aggregated cells is currently not supported.
16
16
  if (cell.getIsPlaceholder())
@@ -3,7 +3,9 @@ import { Grid } from "@mui/material";
3
3
  import { PALETTE } from "../../styles/common/constants/palette";
4
4
  import { mediaTabletDown } from "../../styles/common/mixins/breakpoints";
5
5
  import { FluidPaper } from "../common/Paper/components/FluidPaper/fluidPaper";
6
- export const StyledGridEntityView = styled(Grid) `
6
+ export const StyledGridEntityView = styled(Grid, {
7
+ shouldForwardProp: (prop) => prop !== "bottom" && prop !== "top",
8
+ }) `
7
9
  align-content: flex-start;
8
10
  display: grid;
9
11
  flex: 1;
@@ -11,4 +11,4 @@ import { MarkdownRendererProps } from "./types";
11
11
  * rehype plug-ins are chosen from versions that still target Unified 10:
12
12
  * rehype-raw 7.x, rehype-sanitize 5.x and rehype-react 7.x.
13
13
  */
14
- export declare const MarkdownRenderer: ({ className, components: componentOptions, value, }: MarkdownRendererProps) => JSX.Element;
14
+ export declare const MarkdownRenderer: ({ className, components: componentOptions, regex: markdownRegex, value, }: MarkdownRendererProps) => JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { Typography } from "@mui/material";
2
- import React, { Fragment, createElement, useEffect, useState } from "react";
2
+ import React, { Fragment, createElement, useEffect, useMemo, useState, } from "react";
3
3
  import rehypeRaw from "rehype-raw";
4
4
  import rehypeReact from "rehype-react";
5
5
  import rehypeSanitize from "rehype-sanitize";
@@ -10,6 +10,7 @@ import { unified } from "unified";
10
10
  import { TYPOGRAPHY_PROPS } from "../../styles/common/mui/typography";
11
11
  import { COMPONENTS } from "./constants";
12
12
  import { StyledContainer } from "./markdownRenderer.styles";
13
+ import { rehypeHighlight } from "./rehypeHighlight";
13
14
  /**
14
15
  * Markdown Rendering - Pipeline Version Constraints
15
16
  *
@@ -22,10 +23,11 @@ import { StyledContainer } from "./markdownRenderer.styles";
22
23
  * rehype plug-ins are chosen from versions that still target Unified 10:
23
24
  * rehype-raw 7.x, rehype-sanitize 5.x and rehype-react 7.x.
24
25
  */
25
- export const MarkdownRenderer = ({ className, components: componentOptions = COMPONENTS, value, }) => {
26
+ export const MarkdownRenderer = ({ className, components: componentOptions = COMPONENTS, regex: markdownRegex, value, }) => {
26
27
  const [element, setElement] = useState(null);
27
28
  const [error, setError] = useState(null);
28
29
  const [components] = useState(componentOptions);
30
+ const regex = useMemo(() => markdownRegex, [markdownRegex]);
29
31
  useEffect(() => {
30
32
  let cancelled = false;
31
33
  setError(null);
@@ -35,6 +37,7 @@ export const MarkdownRenderer = ({ className, components: componentOptions = COM
35
37
  .use(remarkRehype, { allowDangerousHtml: true })
36
38
  .use(rehypeRaw)
37
39
  .use(rehypeSanitize)
40
+ .use(rehypeHighlight, { regex })
38
41
  .use(rehypeReact, { Fragment, components, createElement });
39
42
  processor
40
43
  .process(value)
@@ -49,7 +52,7 @@ export const MarkdownRenderer = ({ className, components: componentOptions = COM
49
52
  return () => {
50
53
  cancelled = true;
51
54
  };
52
- }, [components, value]);
55
+ }, [components, regex, value]);
53
56
  if (error)
54
57
  return (React.createElement(Typography, { color: TYPOGRAPHY_PROPS.COLOR.ERROR, variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_SMALL_400 }, error));
55
58
  return React.createElement(StyledContainer, { className: className }, element);
@@ -0,0 +1,10 @@
1
+ import { Root } from "hast";
2
+ /**
3
+ * Custom rehype plugin to highlight markdown from given regex.
4
+ * @param options - Options.
5
+ * @param options.regex - Regex to match.
6
+ * @returns A rehype plugin.
7
+ */
8
+ export declare function rehypeHighlight(options: {
9
+ regex: RegExp | undefined;
10
+ }): (tree: Root) => void;
@@ -0,0 +1,49 @@
1
+ import { visit } from "unist-util-visit";
2
+ /**
3
+ * Custom rehype plugin to highlight markdown from given regex.
4
+ * @param options - Options.
5
+ * @param options.regex - Regex to match.
6
+ * @returns A rehype plugin.
7
+ */
8
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- ignoring for readability
9
+ export function rehypeHighlight(options) {
10
+ const { regex } = options;
11
+ if (!regex)
12
+ return () => { };
13
+ return (tree) => {
14
+ visit(tree, "text", (node, index, parent) => {
15
+ if (!parent)
16
+ return;
17
+ if (typeof index !== "number")
18
+ return;
19
+ // Avoid double marking, breaking scripts, and breaking styles.
20
+ if ("tagName" in parent &&
21
+ ["mark", "script", "style"].includes(parent.tagName)) {
22
+ return;
23
+ }
24
+ const parts = node.value.split(regex);
25
+ if (parts.length === 1)
26
+ return; // No text to highlight.
27
+ const newNodes = [];
28
+ parts.forEach((part, i) => {
29
+ if (!part)
30
+ return; // Skip empties.
31
+ if (i % 2) {
32
+ // Captured text.
33
+ newNodes.push({
34
+ children: [{ type: "text", value: part }],
35
+ properties: {},
36
+ tagName: "mark",
37
+ type: "element",
38
+ });
39
+ }
40
+ else {
41
+ // Normal text.
42
+ newNodes.push({ type: "text", value: part });
43
+ }
44
+ });
45
+ // Replace the original text node with the new nodes.
46
+ parent.children.splice(index, 1, ...newNodes);
47
+ });
48
+ };
49
+ }
@@ -0,0 +1,3 @@
1
+ import { ComponentProps } from "react";
2
+ import { MarkdownRenderer } from "../markdownRenderer";
3
+ export declare const DEFAULT_ARGS: ComponentProps<typeof MarkdownRenderer>;
@@ -0,0 +1,4 @@
1
+ export const DEFAULT_ARGS = {
2
+ regex: /(UBERON|955)/gi,
3
+ value: '| Key | Annotator | Value |\n| --- | --- | --- |\n| `tissue_ontology_term_id` | Curator | categorical with `str` categories. This **MUST** be the UBERON or CL term that best describes the tissue the cell was derived from, depending on the sample type. |\n\n**Mapping guidance**\n\n| For | Use |\n| --- | --- |\n| Tissue | STRONGLY RECOMMENDED to be an UBERON term&nbsp;<br />(e.g. [`UBERON:0008930`](http://purl.obolibrary.org/obo/UBERON_0008930) for a *somatosensory cortex* tissue sample) |\n| Cell culture | MUST be a CL term appended with \\" (cell culture)\\"&nbsp;<br />(e.g. [`CL:0000057`](http://purl.obolibrary.org/obo/CL_0000057) **(cell culture)** for the *WTC-11* cell line) |\n| Organoid | MUST be an UBERON term appended with \\" (organoid)\\"&nbsp;<br />(e.g. [`UBERON:0000955`](http://purl.obolibrary.org/obo/UBERON_0000955) **(organoid)** for a *brain organoid*) |\n| Enriched / sorted / isolated cells from a tissue | MUST be an UBERON or CL term and **SHOULD NOT** use terms that do not capture the tissue of origin.<br /><br />• *CD3+ kidney cells* → [`UBERON:0002113`](https://www.ebi.ac.uk/ols/ontologies/uberon/terms?iri=http://purl.obolibrary.org/obo/UBERON_0002113) (*kidney*) instead of [`CL:000084`](https://www.ebi.ac.uk/ols/ontologies/cl/terms?iri=http://purl.obolibrary.org/obo/CL_0000084) (*T cell*).<br />• *EPCAM+ cervical cells* → [`CL:0000066`](https://www.ebi.ac.uk/ols/ontologies/cl/terms?iri=http://purl.obolibrary.org/obo/CL_0000066) (*epithelial cell* of the cervix). |\n\n---\n\nWhen a dataset is uploaded, the **cellxgene Data Portal** MUST automatically add the matching human-readable name for the corresponding ontology term to the `obs` dataframe. Curators **MUST NOT** annotate the following columns.\n\n### `assay`\n\n| Key | Annotator | Value |\n| --- | --- | --- |\n| `assay` | Data Portal | categorical with `str` categories. This **MUST** be the human-readable name assigned to the value of `assay_ontology_term_id`. |',
4
+ };
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { MarkdownRenderer } from "../markdownRenderer";
3
+ declare const meta: Meta<typeof MarkdownRenderer>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
@@ -0,0 +1,22 @@
1
+ import { Box } from "@mui/material";
2
+ import React from "react";
3
+ import { PALETTE } from "../../../styles/common/constants/palette";
4
+ import { MarkdownRenderer } from "../markdownRenderer";
5
+ import { DEFAULT_ARGS } from "./args";
6
+ const meta = {
7
+ component: MarkdownRenderer,
8
+ decorators: [
9
+ (Story) => (React.createElement(Box, { sx: {
10
+ backgroundColor: PALETTE.COMMON_WHITE,
11
+ fontSize: "14px",
12
+ lineHeight: "20px",
13
+ padding: 3,
14
+ width: 480,
15
+ } },
16
+ React.createElement(Story, null))),
17
+ ],
18
+ };
19
+ export default meta;
20
+ export const Default = {
21
+ args: DEFAULT_ARGS,
22
+ };
@@ -3,5 +3,6 @@ import { BaseComponentProps } from "../types";
3
3
  export type MarkdownRendererComponents = Record<string, ComponentType<any>>;
4
4
  export interface MarkdownRendererProps extends BaseComponentProps {
5
5
  components?: MarkdownRendererComponents;
6
+ regex?: RegExp;
6
7
  value: string;
7
8
  }
@@ -6,11 +6,14 @@ import { RANK_ITEM_OPTIONS } from "./constants";
6
6
  * @returns Array of terms.
7
7
  */
8
8
  export function parseSearchTerms(value) {
9
- return String(value ?? "")
9
+ const terms = String(value ?? "")
10
10
  .toLowerCase()
11
11
  .trim()
12
12
  .split(/\s+/)
13
- .filter(Boolean);
13
+ .filter(Boolean)
14
+ .sort((a, b) => b.length - a.length);
15
+ const termsSet = new Set(terms);
16
+ return [...termsSet];
14
17
  }
15
18
  /**
16
19
  * Ranks a column's value against search terms.
@@ -38,7 +41,6 @@ export function rankColumnValue(row, columnId, terms) {
38
41
  * @param terms - Search terms to match against.
39
42
  */
40
43
  export function rankRowColumns(row, columnId, terms) {
41
- const columnFiltersMeta = row.columnFiltersMeta;
42
44
  // Process other columns.
43
45
  for (const { column } of row.getAllCells()) {
44
46
  // Column is not searchable.
@@ -47,8 +49,6 @@ export function rankRowColumns(row, columnId, terms) {
47
49
  // Column has already been processed.
48
50
  if (column.id === columnId)
49
51
  continue;
50
- if (column.id in columnFiltersMeta)
51
- continue;
52
52
  // Rank the column value.
53
53
  const passed = rankColumnValue(row, column.id, terms);
54
54
  // Add the filter metadata.
@@ -1,4 +1,3 @@
1
1
  import { CellContext, RowData } from "@tanstack/react-table";
2
- import { BaseComponentProps } from "components/types";
3
- import { MarkdownCellProps } from "./types";
4
- export declare const MarkdownCell: <T extends RowData, TValue extends MarkdownCellProps = MarkdownCellProps>({ className, column, getValue, }: BaseComponentProps & CellContext<T, TValue>) => JSX.Element | null;
2
+ import { BaseComponentProps } from "../../../../../types";
3
+ export declare const MarkdownCell: <T extends RowData, TValue extends string = string>({ className, column, getValue, row, table, }: BaseComponentProps & CellContext<T, TValue>) => JSX.Element | null;
@@ -1,13 +1,18 @@
1
1
  import React from "react";
2
2
  import { COMPONENTS } from "../../../../../MarkdownRenderer/constants";
3
+ import { getTokens, getTokensRegex, isRankedCell } from "../RankedCell/utils";
3
4
  import { StyledMarkdownRenderer } from "./markdownCell.styles";
4
- export const MarkdownCell = ({ className, column, getValue, }) => {
5
- const props = getValue();
6
- if (!props)
5
+ export const MarkdownCell = ({ className, column, getValue, row, table, }) => {
6
+ const value = getValue();
7
+ if (!value)
7
8
  return null;
8
- const { values } = props;
9
+ // Get column metadata (components to be rendered in MarkdownRenderer).
9
10
  const columnDef = column?.columnDef;
10
11
  const columnMeta = columnDef?.meta;
11
12
  const components = columnMeta?.components;
12
- return (React.createElement(StyledMarkdownRenderer, { className: className, components: { ...COMPONENTS, ...components }, value: values }));
13
+ // Determine if the cell is ranked.
14
+ const isRanked = isRankedCell(table, row, column.id);
15
+ // Build regex for markdown highlighting.
16
+ const regex = isRanked ? getTokensRegex(getTokens(table)) : undefined;
17
+ return (React.createElement(StyledMarkdownRenderer, { className: className, components: { ...COMPONENTS, ...components }, regex: regex, value: value }));
13
18
  };
@@ -1,10 +1,12 @@
1
1
  export const DEFAULT_ARGS = {
2
- getValue: (() => ({
3
- values: '| Key | Annotator | Value |\n| --- | --- | --- |\n| `tissue_ontology_term_id` | Curator | categorical with `str` categories. This **MUST** be the UBERON or CL term that best describes the tissue the cell was derived from, depending on the sample type. |\n\n**Mapping guidance**\n\n| For | Use |\n| --- | --- |\n| Tissue | STRONGLY RECOMMENDED to be an UBERON term&nbsp;<br />(e.g. [`UBERON:0008930`](http://purl.obolibrary.org/obo/UBERON_0008930) for a *somatosensory cortex* tissue sample) |\n| Cell culture | MUST be a CL term appended with \\" (cell culture)\\"&nbsp;<br />(e.g. [`CL:0000057`](http://purl.obolibrary.org/obo/CL_0000057) **(cell culture)** for the *WTC-11* cell line) |\n| Organoid | MUST be an UBERON term appended with \\" (organoid)\\"&nbsp;<br />(e.g. [`UBERON:0000955`](http://purl.obolibrary.org/obo/UBERON_0000955) **(organoid)** for a *brain organoid*) |\n| Enriched / sorted / isolated cells from a tissue | MUST be an UBERON or CL term and **SHOULD NOT** use terms that do not capture the tissue of origin.<br /><br />• *CD3+ kidney cells* → [`UBERON:0002113`](https://www.ebi.ac.uk/ols/ontologies/uberon/terms?iri=http://purl.obolibrary.org/obo/UBERON_0002113) (*kidney*) instead of [`CL:000084`](https://www.ebi.ac.uk/ols/ontologies/cl/terms?iri=http://purl.obolibrary.org/obo/CL_0000084) (*T cell*).<br />• *EPCAM+ cervical cells* → [`CL:0000066`](https://www.ebi.ac.uk/ols/ontologies/cl/terms?iri=http://purl.obolibrary.org/obo/CL_0000066) (*epithelial cell* of the cervix). |\n\n---\n\nWhen a dataset is uploaded, the **cellxgene Data Portal** MUST automatically add the matching human-readable name for the corresponding ontology term to the `obs` dataframe. Curators **MUST NOT** annotate the following columns.\n\n### `assay`\n\n| Key | Annotator | Value |\n| --- | --- | --- |\n| `assay` | Data Portal | categorical with `str` categories. This **MUST** be the human-readable name assigned to the value of `assay_ontology_term_id`. |',
4
- })),
2
+ column: { id: "description" },
3
+ getValue: () => '| Key | Annotator | Value |\n| --- | --- | --- |\n| `tissue_ontology_term_id` | Curator | categorical with `str` categories. This **MUST** be the UBERON or CL term that best describes the tissue the cell was derived from, depending on the sample type. |\n\n**Mapping guidance**\n\n| For | Use |\n| --- | --- |\n| Tissue | STRONGLY RECOMMENDED to be an UBERON term&nbsp;<br />(e.g. [`UBERON:0008930`](http://purl.obolibrary.org/obo/UBERON_0008930) for a *somatosensory cortex* tissue sample) |\n| Cell culture | MUST be a CL term appended with \\" (cell culture)\\"&nbsp;<br />(e.g. [`CL:0000057`](http://purl.obolibrary.org/obo/CL_0000057) **(cell culture)** for the *WTC-11* cell line) |\n| Organoid | MUST be an UBERON term appended with \\" (organoid)\\"&nbsp;<br />(e.g. [`UBERON:0000955`](http://purl.obolibrary.org/obo/UBERON_0000955) **(organoid)** for a *brain organoid*) |\n| Enriched / sorted / isolated cells from a tissue | MUST be an UBERON or CL term and **SHOULD NOT** use terms that do not capture the tissue of origin.<br /><br />• *CD3+ kidney cells* → [`UBERON:0002113`](https://www.ebi.ac.uk/ols/ontologies/uberon/terms?iri=http://purl.obolibrary.org/obo/UBERON_0002113) (*kidney*) instead of [`CL:000084`](https://www.ebi.ac.uk/ols/ontologies/cl/terms?iri=http://purl.obolibrary.org/obo/CL_0000084) (*T cell*).<br />• *EPCAM+ cervical cells* → [`CL:0000066`](https://www.ebi.ac.uk/ols/ontologies/cl/terms?iri=http://purl.obolibrary.org/obo/CL_0000066) (*epithelial cell* of the cervix). |\n\n---\n\nWhen a dataset is uploaded, the **cellxgene Data Portal** MUST automatically add the matching human-readable name for the corresponding ontology term to the `obs` dataframe. Curators **MUST NOT** annotate the following columns.\n\n### `assay`\n\n| Key | Annotator | Value |\n| --- | --- | --- |\n| `assay` | Data Portal | categorical with `str` categories. This **MUST** be the human-readable name assigned to the value of `assay_ontology_term_id`. |',
4
+ row: { columnFiltersMeta: { description: { passed: false } } },
5
+ table: { getState: () => ({ globalFilter: "" }) },
5
6
  };
6
7
  export const WITH_HTML_ARGS = {
7
- getValue: (() => ({
8
- values: "Hello <br />World <a href='https://www.example.com'>example link</a>",
9
- })),
8
+ column: { id: "description" },
9
+ getValue: () => "Hello <br />World <a href='https://www.example.com'>example link</a>",
10
+ row: { columnFiltersMeta: { description: { passed: false } } },
11
+ table: { getState: () => ({ globalFilter: "" }) },
10
12
  };
@@ -1,3 +1,3 @@
1
1
  import { CellContext, RowData } from "@tanstack/react-table";
2
- import { BaseComponentProps } from "../../../../../types";
3
- export declare const RankedCell: <T extends RowData, TValue = string | undefined | null>({ className, column, getValue, row, table, }: BaseComponentProps & CellContext<T, TValue>) => JSX.Element | null;
2
+ import { BaseComponentProps, ChildrenProps } from "../../../../../types";
3
+ export declare const RankedCell: <T extends RowData, TValue = string | undefined | null>({ children, className, column, getValue, row, table, }: BaseComponentProps & CellContext<T, TValue> & ChildrenProps) => JSX.Element | null;
@@ -1,6 +1,8 @@
1
1
  import React, { Fragment } from "react";
2
2
  import { getTokens, getTokensRegex, isRankedCell } from "./utils";
3
- export const RankedCell = ({ className, column, getValue, row, table, }) => {
3
+ export const RankedCell = ({
4
+ /* children - e.g. AnchorLink component */
5
+ children, className, column, getValue, row, table, }) => {
4
6
  // Get the cell value.
5
7
  const value = getValue();
6
8
  // If the cell value is undefined or null, return null.
@@ -12,14 +14,20 @@ export const RankedCell = ({ className, column, getValue, row, table, }) => {
12
14
  const isRanked = isRankedCell(table, row, column.id);
13
15
  // Return the unranked cell, as-is.
14
16
  if (!isRanked)
15
- return React.createElement(Fragment, null, stringValue);
17
+ return (React.createElement(Fragment, null,
18
+ stringValue,
19
+ children));
16
20
  // Tokenise the current global filter.
17
21
  const tokens = getTokens(table);
18
22
  // If there are no tokens, return the value as-is.
19
23
  if (tokens.length === 0)
20
- return React.createElement(Fragment, null, stringValue);
24
+ return (React.createElement(Fragment, null,
25
+ stringValue,
26
+ children));
21
27
  // Create regex pattern.
22
28
  const regex = getTokensRegex(tokens);
23
29
  // Return the ranked cell, with highlighting.
24
- return (React.createElement("span", { className: className }, stringValue.split(regex).map((part, i) => (React.createElement(Fragment, { key: i }, part.match(regex) ? React.createElement("mark", null, part) : part)))));
30
+ return (React.createElement("span", { className: className },
31
+ stringValue.split(regex).map((part, i) => (React.createElement(Fragment, { key: i }, part.match(regex) ? React.createElement("mark", null, part) : part))),
32
+ children));
25
33
  };
@@ -1,13 +1,4 @@
1
1
  import { Row, RowData, Table } from "@tanstack/react-table";
2
- /**
3
- * Renders a cell value with highlighting if it matches item rank filter criteria.
4
- * @param table - Table.
5
- * @param row - Row.
6
- * @param columnId - Column identifier.
7
- * @param value - Cell value.
8
- * @returns Rendered cell value with highlighting.
9
- */
10
- export declare function renderRankedCell<T extends RowData>(table: Table<T>, row: Row<T>, columnId: string, value: string | undefined | null): string;
11
2
  /**
12
3
  * Returns the current global filter tokens from the table.
13
4
  * @param table - Table.
@@ -1,32 +1,5 @@
1
1
  import { escapeRegExp } from "../../../../../../common/utils";
2
2
  import { parseSearchTerms } from "../../../../columnDef/globalFilter/utils";
3
- /**
4
- * Renders a cell value with highlighting if it matches item rank filter criteria.
5
- * @param table - Table.
6
- * @param row - Row.
7
- * @param columnId - Column identifier.
8
- * @param value - Cell value.
9
- * @returns Rendered cell value with highlighting.
10
- */
11
- export function renderRankedCell(table, row, columnId, value) {
12
- // If the cell value is undefined or null, return an empty string.
13
- if (value === undefined || value === null)
14
- return "";
15
- const stringValue = String(value);
16
- // Check if the cell is ranked.
17
- const isRanked = isRankedCell(table, row, columnId);
18
- // Return the unranked cell, as-is, in string form.
19
- if (!isRanked)
20
- return stringValue;
21
- // Tokenise the current global filter.
22
- const tokens = getTokens(table);
23
- // If there are no tokens, return the value as-is, in string form.
24
- if (tokens.length === 0)
25
- return stringValue;
26
- // Create regex pattern.
27
- const regex = getTokensRegex(tokens);
28
- return stringValue.replace(regex, "<mark>$1</mark>");
29
- }
30
3
  /**
31
4
  * Returns the current global filter tokens from the table.
32
5
  * @param table - Table.
@@ -1,3 +1,4 @@
1
+ import { MouseEvent } from "react";
1
2
  /**
2
3
  * An anchor link component that provides deep linking functionality.
3
4
  * @important The parent element must have `position: relative` CSS property
@@ -7,6 +8,7 @@
7
8
  interface AnchorLinkProps {
8
9
  anchorLink: string;
9
10
  className?: string;
11
+ onClick?: (e: MouseEvent) => void;
10
12
  }
11
- export declare const AnchorLink: ({ anchorLink, className, }: AnchorLinkProps) => JSX.Element;
13
+ export declare const AnchorLink: ({ anchorLink, className, onClick, }: AnchorLinkProps) => JSX.Element;
12
14
  export {};
@@ -2,8 +2,8 @@ import { LinkRounded } from "@mui/icons-material";
2
2
  import { useRouter } from "next/router";
3
3
  import React from "react";
4
4
  import { StyledNextLink } from "./anchorLink.styles";
5
- export const AnchorLink = ({ anchorLink, className, }) => {
5
+ export const AnchorLink = ({ anchorLink, className, onClick, }) => {
6
6
  const { query } = useRouter();
7
- return (React.createElement(StyledNextLink, { "aria-label": anchorLink, className: className, href: { hash: anchorLink, query } },
7
+ return (React.createElement(StyledNextLink, { "aria-label": anchorLink, className: className, href: { hash: anchorLink, query }, onClick: onClick },
8
8
  React.createElement(LinkRounded, { fontSize: "xsmall" })));
9
9
  };
@@ -1,8 +1,8 @@
1
1
  import styled from "@emotion/styled";
2
2
  import Link from "next/link";
3
- import { inkLight } from "../../../styles/common/mixins/colors";
3
+ import { PALETTE } from "../../../styles/common/constants/palette";
4
4
  export const StyledNextLink = styled(Link) `
5
- color: ${inkLight};
5
+ color: ${PALETTE.INK_LIGHT};
6
6
  margin-left: 4px;
7
7
  opacity: 0;
8
8
  position: absolute;
@@ -0,0 +1,2 @@
1
+ import { CSSPropHyphen } from "./types";
2
+ export declare const PROPERTY: Record<string, CSSPropHyphen>;
@@ -0,0 +1,3 @@
1
+ export const PROPERTY = {
2
+ SCROLL_PADDING_TOP: "scroll-padding-top",
3
+ };
@@ -0,0 +1,2 @@
1
+ import { CSSPropHyphen } from "./types";
2
+ export declare const useHtmlStyle: (property: CSSPropHyphen, value: string | null) => void;