@reltio/design 0.2.0 → 0.3.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 (102) hide show
  1. package/charts/SetOverlapChart/SetOverlapChart.d.ts +33 -0
  2. package/charts/SetOverlapChart/SetOverlapChart.js +79 -0
  3. package/charts/SetOverlapChart/SetOverlapChart.module.css.d.ts +2 -0
  4. package/charts/SetOverlapChart/SetOverlapChart.module.css.js +36 -0
  5. package/charts/SetOverlapChart/SetOverlapChart.module.css.json +1 -0
  6. package/charts/SetOverlapChart/SetOverlapChart.types.d.ts +117 -0
  7. package/charts/SetOverlapChart/SetOverlapChart.types.js +1 -0
  8. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.d.ts +2 -0
  9. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.js +36 -0
  10. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.module.css.d.ts +2 -0
  11. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.module.css.js +40 -0
  12. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.module.css.json +1 -0
  13. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.types.d.ts +15 -0
  14. package/charts/SetOverlapChart/components/IntersectionsChart/IntersectionsChart.types.js +1 -0
  15. package/charts/SetOverlapChart/components/IntersectionsChart/helpers.d.ts +4 -0
  16. package/charts/SetOverlapChart/components/IntersectionsChart/helpers.js +4 -0
  17. package/charts/SetOverlapChart/components/IntersectionsChart/index.d.ts +1 -0
  18. package/charts/SetOverlapChart/components/IntersectionsChart/index.js +1 -0
  19. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.d.ts +2 -0
  20. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.js +30 -0
  21. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.module.css.d.ts +2 -0
  22. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.module.css.js +36 -0
  23. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.module.css.json +1 -0
  24. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.types.d.ts +8 -0
  25. package/charts/SetOverlapChart/components/IntersectionsChartAxis/IntersectionsChartAxis.types.js +1 -0
  26. package/charts/SetOverlapChart/components/IntersectionsChartAxis/helpers.d.ts +1 -0
  27. package/charts/SetOverlapChart/components/IntersectionsChartAxis/helpers.js +3 -0
  28. package/charts/SetOverlapChart/components/IntersectionsChartAxis/index.d.ts +1 -0
  29. package/charts/SetOverlapChart/components/IntersectionsChartAxis/index.js +1 -0
  30. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.d.ts +2 -0
  31. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.js +13 -0
  32. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.module.css.d.ts +2 -0
  33. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.module.css.js +29 -0
  34. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.module.css.json +1 -0
  35. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.types.d.ts +22 -0
  36. package/charts/SetOverlapChart/components/IntersectionsMatrix/IntersectionsMatrix.types.js +1 -0
  37. package/charts/SetOverlapChart/components/IntersectionsMatrix/helpers.d.ts +7 -0
  38. package/charts/SetOverlapChart/components/IntersectionsMatrix/helpers.js +25 -0
  39. package/charts/SetOverlapChart/components/IntersectionsMatrix/index.d.ts +1 -0
  40. package/charts/SetOverlapChart/components/IntersectionsMatrix/index.js +1 -0
  41. package/charts/SetOverlapChart/components/SetsChart/SetsChart.d.ts +2 -0
  42. package/charts/SetOverlapChart/components/SetsChart/SetsChart.js +54 -0
  43. package/charts/SetOverlapChart/components/SetsChart/SetsChart.module.css.d.ts +2 -0
  44. package/charts/SetOverlapChart/components/SetsChart/SetsChart.module.css.js +55 -0
  45. package/charts/SetOverlapChart/components/SetsChart/SetsChart.module.css.json +1 -0
  46. package/charts/SetOverlapChart/components/SetsChart/SetsChart.types.d.ts +16 -0
  47. package/charts/SetOverlapChart/components/SetsChart/SetsChart.types.js +1 -0
  48. package/charts/SetOverlapChart/components/SetsChart/index.d.ts +1 -0
  49. package/charts/SetOverlapChart/components/SetsChart/index.js +1 -0
  50. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.d.ts +2 -0
  51. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.js +9 -0
  52. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.module.css.d.ts +2 -0
  53. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.module.css.js +24 -0
  54. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.module.css.json +1 -0
  55. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.types.d.ts +4 -0
  56. package/charts/SetOverlapChart/components/SetsChartAxis/SetsChartAxis.types.js +1 -0
  57. package/charts/SetOverlapChart/components/SetsChartAxis/index.d.ts +1 -0
  58. package/charts/SetOverlapChart/components/SetsChartAxis/index.js +1 -0
  59. package/charts/SetOverlapChart/constants.d.ts +14 -0
  60. package/charts/SetOverlapChart/constants.js +16 -0
  61. package/charts/SetOverlapChart/helpers.d.ts +15 -0
  62. package/charts/SetOverlapChart/helpers.js +52 -0
  63. package/charts/SetOverlapChart/index.d.ts +2 -0
  64. package/charts/SetOverlapChart/index.js +1 -0
  65. package/charts/SetOverlapChart/useSetOverlapChartSizes.d.ts +19 -0
  66. package/charts/SetOverlapChart/useSetOverlapChartSizes.js +35 -0
  67. package/charts/index.d.ts +1 -0
  68. package/charts/index.js +1 -0
  69. package/components/AppSelector/AppSelector.d.ts +5 -0
  70. package/components/AppSelector/AppSelector.js +20 -0
  71. package/components/AppSelector/AppSelector.module.css.d.ts +2 -0
  72. package/components/AppSelector/AppSelector.module.css.js +61 -0
  73. package/components/AppSelector/AppSelector.module.css.json +1 -0
  74. package/components/AppSelector/AppSelector.types.d.ts +29 -0
  75. package/components/AppSelector/AppSelector.types.js +1 -0
  76. package/components/AppSelector/index.d.ts +2 -0
  77. package/components/AppSelector/index.js +1 -0
  78. package/components/Button/Button.d.ts +3 -0
  79. package/components/Button/Button.js +9 -3
  80. package/components/Button/Button.module.css.js +9 -3
  81. package/components/Button/Button.module.css.json +1 -1
  82. package/components/Divider/Divider.d.ts +2 -0
  83. package/components/Divider/Divider.js +6 -0
  84. package/components/Divider/Divider.module.css.d.ts +2 -0
  85. package/components/Divider/Divider.module.css.js +40 -0
  86. package/components/Divider/Divider.module.css.json +1 -0
  87. package/components/Divider/Divider.types.d.ts +11 -0
  88. package/components/Divider/Divider.types.js +1 -0
  89. package/components/Divider/index.d.ts +2 -0
  90. package/components/Divider/index.js +1 -0
  91. package/components/Popover/Popover.d.ts +36 -0
  92. package/components/Popover/Popover.js +62 -0
  93. package/components/Popover/Popover.module.css.d.ts +2 -0
  94. package/components/Popover/Popover.module.css.js +62 -0
  95. package/components/Popover/Popover.module.css.json +1 -0
  96. package/components/Popover/Popover.types.d.ts +18 -0
  97. package/components/Popover/Popover.types.js +1 -0
  98. package/components/Popover/index.d.ts +2 -0
  99. package/components/Popover/index.js +1 -0
  100. package/components/index.d.ts +1 -0
  101. package/components/index.js +1 -0
  102. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ export { IntersectionsMatrix } from "./IntersectionsMatrix";
@@ -0,0 +1,2 @@
1
+ import type { SetsChartProps } from "./SetsChart.types";
2
+ export declare const SetsChart: ({ mode, sets, intersections, width, yScale, transform, labels, hoveredSet, hoveredIntersection, hoverAreaWidth, axisLabel, onSetHover, }: SetsChartProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { scaleLinear } from "d3-scale";
3
+ import { useMemo } from "react";
4
+ import { classNames } from "../../../../utils/classNames";
5
+ import { GAP_BEFORE_LEFT_BAR, SET_BAR_HEIGHT, SET_LABEL_SIZE, TEXT_GAP, } from "../../constants";
6
+ import { SetsChartAxis } from "../SetsChartAxis";
7
+ import styles from "./SetsChart.module.css";
8
+ export const SetsChart = ({ mode, sets, intersections, width, yScale, transform, labels, hoveredSet, hoveredIntersection, hoverAreaWidth, axisLabel, onSetHover, }) => {
9
+ const hoveredElementsSet = useMemo(() => {
10
+ const elements = hoveredIntersection?.elements || hoveredSet?.elements;
11
+ return elements ? new Set(elements) : null;
12
+ }, [hoveredIntersection, hoveredSet]);
13
+ const setOverlappingColumns = useMemo(() => {
14
+ const result = {};
15
+ sets.forEach((set) => {
16
+ result[set.name] = intersections.filter((intersection) => intersection.sets.includes(set.name));
17
+ });
18
+ return result;
19
+ }, [sets, intersections]);
20
+ const xScale = useMemo(() => {
21
+ const maxSetSize = sets.length > 0 ? Math.max(...sets.map((set) => set.size)) : 0;
22
+ return scaleLinear().domain([0, maxSetSize]).range([0, width]);
23
+ }, [width, sets]);
24
+ const rootStyle = {
25
+ "--set-label-size": SET_LABEL_SIZE,
26
+ };
27
+ return (_jsxs("g", { transform: transform, style: rootStyle, role: "listbox", "aria-label": "Set bars", children: [_jsx(SetsChartAxis, { width: width, label: axisLabel }), sets.map((set, i) => {
28
+ const { name, size, elements } = set;
29
+ const label = labels[i];
30
+ const height = yScale.bandwidth();
31
+ const y = (yScale(name) ?? 0) + height / 2 - SET_BAR_HEIGHT / 2;
32
+ const lineY = (yScale(name) ?? 0) + height;
33
+ let barSize = size;
34
+ if (mode === "distinctIntersection" &&
35
+ (hoveredIntersection || hoveredSet)) {
36
+ if (hoveredIntersection) {
37
+ const containsSet = hoveredIntersection?.sets.includes(name);
38
+ barSize = containsSet ? hoveredIntersection?.size : 0;
39
+ }
40
+ else if (hoveredSet && setOverlappingColumns[hoveredSet.name]) {
41
+ const hoveredSetOverlappingColumns = new Set(setOverlappingColumns[hoveredSet.name]);
42
+ barSize = setOverlappingColumns[set.name]
43
+ .filter((item) => hoveredSetOverlappingColumns.has(item))
44
+ .reduce((acc, item) => acc + item.size, 0);
45
+ }
46
+ }
47
+ else {
48
+ barSize = hoveredElementsSet
49
+ ? elements.filter((el) => hoveredElementsSet.has(el)).length
50
+ : size;
51
+ }
52
+ return (_jsxs("g", { children: [_jsx("rect", { className: classNames(styles.setsBarBackground), x: 0, y: y, width: width, height: SET_BAR_HEIGHT }), _jsx("rect", { className: classNames(styles.setsBarDimmed), x: width - xScale(size), y: y, width: xScale(size), height: SET_BAR_HEIGHT }), _jsx("rect", { className: classNames(styles.setsBar), x: width - xScale(barSize), y: y, width: xScale(barSize), height: SET_BAR_HEIGHT }), i !== sets.length - 1 && (_jsx("line", { className: classNames(styles.gridLine), x1: 0, x2: hoverAreaWidth, y1: lineY, y2: lineY })), _jsx("text", { className: classNames(styles.setsLabel), x: width + TEXT_GAP, y: (yScale(name) ?? 0) + height / 2, children: label }), _jsx("rect", { className: classNames(styles.hoverBar), "data-reltio-id": "set-hover-area", role: "option", "aria-selected": hoveredSet === set, x: -GAP_BEFORE_LEFT_BAR + 1, y: yScale(name), width: hoverAreaWidth - 2, height: height, tabIndex: 0, "aria-label": `Set: ${name}, size: ${size}`, onMouseEnter: () => onSetHover(set), onMouseLeave: () => onSetHover(null), onFocus: () => onSetHover(set), onBlur: () => onSetHover(null) })] }, `set-group-${name}`));
53
+ })] }));
54
+ };
@@ -0,0 +1,2 @@
1
+ import json from './SetsChart.module.css.json';
2
+ export default json;
@@ -0,0 +1,55 @@
1
+ import styleInject from 'style-inject';
2
+ import json from './SetsChart.module.css.json';
3
+ styleInject(`
4
+ .SetsChart_setsBarBackground__L29wd {
5
+ fill: var(--reltio-color-brand-blue);
6
+ opacity: 0.08;
7
+ transition:
8
+ x ease-in-out 0.3s,
9
+ width ease-in-out 0.3s;
10
+ }
11
+
12
+ .SetsChart_setsBarDimmed__L29wd {
13
+ fill: var(--reltio-color-border-3);
14
+ transition:
15
+ x ease-in-out 0.3s,
16
+ width ease-in-out 0.3s;
17
+ }
18
+
19
+ .SetsChart_setsBar__L29wd {
20
+ fill: var(--reltio-color-brand-blue);
21
+ transition:
22
+ x ease-in-out 0.3s,
23
+ width ease-in-out 0.3s;
24
+ }
25
+
26
+ .SetsChart_hoverBar__L29wd {
27
+ fill: transparent;
28
+ stroke-width: 1px;
29
+ cursor: pointer;
30
+ stroke: var(--reltio-color-brand-blue);
31
+ opacity: 0;
32
+ transition: opacity ease-in-out 0.3s;
33
+ outline: none;
34
+
35
+ &:hover,
36
+ &:focus-visible {
37
+ opacity: 1;
38
+ }
39
+ }
40
+
41
+ .SetsChart_setsLabel__L29wd {
42
+ font-size: var(--set-label-size);
43
+ fill: var(--reltio-color-text);
44
+ text-anchor: start;
45
+ dominant-baseline: middle;
46
+ transition: all ease-in-out 0.3s;
47
+ }
48
+
49
+ .SetsChart_gridLine__L29wd {
50
+ stroke: var(--reltio-color-border-black);
51
+ opacity: 0.04;
52
+ stroke-width: 1px;
53
+ }
54
+ `);
55
+ export default json;
@@ -0,0 +1 @@
1
+ { "setsBarBackground": "SetsChart_setsBarBackground__L29wd", "setsBarDimmed": "SetsChart_setsBarDimmed__L29wd", "setsBar": "SetsChart_setsBar__L29wd", "hoverBar": "SetsChart_hoverBar__L29wd", "setsLabel": "SetsChart_setsLabel__L29wd", "gridLine": "SetsChart_gridLine__L29wd" }
@@ -0,0 +1,16 @@
1
+ import type { ScaleBand } from "d3-scale";
2
+ import type { DataSet, Intersection, SetOverlapChartMode } from "../../SetOverlapChart.types";
3
+ export type SetsChartProps = {
4
+ mode: SetOverlapChartMode;
5
+ sets: DataSet[];
6
+ intersections: Intersection[];
7
+ width: number;
8
+ yScale: ScaleBand<string>;
9
+ labels: string[];
10
+ transform?: string;
11
+ hoverAreaWidth: number;
12
+ hoveredSet?: DataSet | null;
13
+ hoveredIntersection?: Intersection | null;
14
+ axisLabel?: string;
15
+ onSetHover: (set: DataSet | null) => void;
16
+ };
@@ -0,0 +1 @@
1
+ export { SetsChart } from "./SetsChart";
@@ -0,0 +1 @@
1
+ export { SetsChart } from "./SetsChart";
@@ -0,0 +1,2 @@
1
+ import type { SetsChartAxisProps } from "./SetsChartAxis.types";
2
+ export declare const SetsChartAxis: ({ width, label }: SetsChartAxisProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { classNames } from "../../../../utils/classNames";
3
+ import styles from "./SetsChartAxis.module.css";
4
+ const AXIS_LABEL_OFFSET = 12;
5
+ const AXIS_TICK_LABEL_OFFSET = 9;
6
+ const LABEL_OFFSET = AXIS_LABEL_OFFSET + AXIS_TICK_LABEL_OFFSET;
7
+ export const SetsChartAxis = ({ width, label }) => {
8
+ return (_jsxs("g", { children: [_jsxs("g", { transform: "translate(0,0)", children: [_jsx("line", { y2: -6, className: classNames(styles.axisTick) }), _jsx("text", { x: 0, y: -AXIS_TICK_LABEL_OFFSET, className: classNames(styles.axisTickLabel), children: "100%" })] }), _jsxs("g", { transform: `translate(${width},0)`, children: [_jsx("line", { y2: -6, className: classNames(styles.axisTick) }), _jsx("text", { x: 0, y: -AXIS_TICK_LABEL_OFFSET, className: classNames(styles.axisTickLabel), children: "0%" })] }), label && (_jsx("text", { className: classNames(styles.axisLabel), transform: `translate(${width / 2},${-LABEL_OFFSET})`, children: label }))] }));
9
+ };
@@ -0,0 +1,2 @@
1
+ import json from './SetsChartAxis.module.css.json';
2
+ export default json;
@@ -0,0 +1,24 @@
1
+ import styleInject from 'style-inject';
2
+ import json from './SetsChartAxis.module.css.json';
3
+ styleInject(`
4
+ .SetsChartAxis_axisTick__L29wd {
5
+ stroke: var(--reltio-color-border-black);
6
+ stroke-width: 1px;
7
+ opacity: 0.54;
8
+ }
9
+
10
+ .SetsChartAxis_axisTickLabel__L29wd {
11
+ font-size: 10px;
12
+ fill: var(--reltio-color-text);
13
+ opacity: 0.54;
14
+ text-anchor: middle;
15
+ }
16
+
17
+ .SetsChartAxis_axisLabel__L29wd {
18
+ fill: var(--reltio-color-text);
19
+ opacity: 0.54;
20
+ font-size: 12px;
21
+ text-anchor: middle;
22
+ }
23
+ `);
24
+ export default json;
@@ -0,0 +1 @@
1
+ { "axisTick": "SetsChartAxis_axisTick__L29wd", "axisTickLabel": "SetsChartAxis_axisTickLabel__L29wd", "axisLabel": "SetsChartAxis_axisLabel__L29wd" }
@@ -0,0 +1,4 @@
1
+ export type SetsChartAxisProps = {
2
+ width: number;
3
+ label?: string;
4
+ };
@@ -0,0 +1 @@
1
+ export { SetsChartAxis } from "./SetsChartAxis";
@@ -0,0 +1 @@
1
+ export { SetsChartAxis } from "./SetsChartAxis";
@@ -0,0 +1,14 @@
1
+ export declare const TOP_GAP = 24;
2
+ export declare const TEXT_GAP = 16;
3
+ export declare const GAP_BETWEEN_TABLE_AND_CHART = 8;
4
+ export declare const GAP_BEFORE_LEFT_BAR = 12;
5
+ export declare const AXIS_TICK_LABEL_SIZE = "12px";
6
+ export declare const SET_LABEL_SIZE = "13px";
7
+ export declare const SET_BAR_HEIGHT = 12;
8
+ export declare const MATRIX_ROW_HEIGHT = 29;
9
+ export declare const MIN_MATRIX_COLUMN_WIDTH = 28;
10
+ export declare const MIN_INTERSECTION_CHART_HEIGHT = 120;
11
+ export declare const MIN_SETS_CHART_WIDTH = 100;
12
+ export declare const MAX_SETS_CHART_WIDTH = 200;
13
+ export declare const MAX_CHARACTERS = 20;
14
+ export declare const MIN_CHARACTERS = 8;
@@ -0,0 +1,16 @@
1
+ // Gaps
2
+ export const TOP_GAP = 24;
3
+ export const TEXT_GAP = 16;
4
+ export const GAP_BETWEEN_TABLE_AND_CHART = 8;
5
+ export const GAP_BEFORE_LEFT_BAR = 12;
6
+ // Sizes
7
+ export const AXIS_TICK_LABEL_SIZE = "12px";
8
+ export const SET_LABEL_SIZE = "13px";
9
+ export const SET_BAR_HEIGHT = 12;
10
+ export const MATRIX_ROW_HEIGHT = 29;
11
+ export const MIN_MATRIX_COLUMN_WIDTH = 28;
12
+ export const MIN_INTERSECTION_CHART_HEIGHT = 120;
13
+ export const MIN_SETS_CHART_WIDTH = 100;
14
+ export const MAX_SETS_CHART_WIDTH = 200;
15
+ export const MAX_CHARACTERS = 20;
16
+ export const MIN_CHARACTERS = 8;
@@ -0,0 +1,15 @@
1
+ import type { DataSet } from "./SetOverlapChart.types";
2
+ export declare const measureText: (text: string, fontSize: string) => number;
3
+ export declare const truncateLabels: (sets: DataSet[], maxLabelsWidth: number) => {
4
+ labels: string[];
5
+ maxWidth: number;
6
+ };
7
+ export declare const createMatrixXScale: (intersectionCount: number, width: number) => import("d3-scale").ScaleBand<string>;
8
+ export declare const createMatrixYScale: (setNames: string[], height: number) => import("d3-scale").ScaleBand<string>;
9
+ export declare const getEmptyGridDimensions: (width: number, height: number) => {
10
+ width: number;
11
+ height: number;
12
+ left: number;
13
+ };
14
+ export declare const createEmptyGridXScale: (width: number) => import("d3-scale").ScaleBand<string>;
15
+ export declare const createEmptyGridYScale: (height: number) => import("d3-scale").ScaleLinear<number, number, never>;
@@ -0,0 +1,52 @@
1
+ import { scaleBand, scaleLinear } from "d3-scale";
2
+ import { MAX_CHARACTERS, MIN_CHARACTERS, SET_LABEL_SIZE, TOP_GAP, } from "./constants";
3
+ const ctxCache = new Map();
4
+ const getContext = (fontSize) => {
5
+ const cached = ctxCache.get(fontSize);
6
+ if (cached)
7
+ return cached;
8
+ if (typeof OffscreenCanvas !== "undefined") {
9
+ const ctx = new OffscreenCanvas(0, 0).getContext("2d");
10
+ if (ctx) {
11
+ ctx.font = `${fontSize} "Inter", sans-serif`;
12
+ ctxCache.set(fontSize, ctx);
13
+ return ctx;
14
+ }
15
+ }
16
+ return null;
17
+ };
18
+ export const measureText = (text, fontSize) => {
19
+ const ctx = getContext(fontSize);
20
+ if (ctx)
21
+ return ctx.measureText(text).width;
22
+ return text.length * Number.parseFloat(fontSize) * 0.6;
23
+ };
24
+ export const truncateLabels = (sets, maxLabelsWidth) => {
25
+ const newTruncatedLabels = [];
26
+ let maxWidth = 0;
27
+ sets.forEach(({ name }) => {
28
+ let truncatedText = name.length > MAX_CHARACTERS
29
+ ? `${name.slice(0, MAX_CHARACTERS)}...`
30
+ : name;
31
+ let textWidth = measureText(truncatedText, SET_LABEL_SIZE);
32
+ while (textWidth > maxLabelsWidth &&
33
+ truncatedText.length > MIN_CHARACTERS) {
34
+ truncatedText = `${truncatedText.slice(0, -4)}...`;
35
+ textWidth = measureText(truncatedText, SET_LABEL_SIZE);
36
+ }
37
+ newTruncatedLabels.push(truncatedText);
38
+ maxWidth = Math.max(maxWidth, textWidth);
39
+ });
40
+ return { labels: newTruncatedLabels, maxWidth };
41
+ };
42
+ export const createMatrixXScale = (intersectionCount, width) => scaleBand([0, width])
43
+ .domain(Array.from({ length: intersectionCount }, (_, i) => String(i)))
44
+ .paddingInner(0.2);
45
+ export const createMatrixYScale = (setNames, height) => scaleBand([0, height]).domain(setNames);
46
+ export const getEmptyGridDimensions = (width, height) => ({
47
+ width: width * 0.9,
48
+ height: height - TOP_GAP * 2,
49
+ left: (width - width * 0.9) / 2,
50
+ });
51
+ export const createEmptyGridXScale = (width) => scaleBand().domain([]).range([0, width]);
52
+ export const createEmptyGridYScale = (height) => scaleLinear().domain([0, 1000]).range([height, 0]);
@@ -0,0 +1,2 @@
1
+ export { SetOverlapChart } from "./SetOverlapChart";
2
+ export type { DataSet, Intersection, SetOverlapChartMode, SetOverlapChartProps, } from "./SetOverlapChart.types";
@@ -0,0 +1 @@
1
+ export { SetOverlapChart } from "./SetOverlapChart";
@@ -0,0 +1,19 @@
1
+ import type { DataSet, Intersection } from "./SetOverlapChart.types";
2
+ type Props = {
3
+ width: number;
4
+ height: number;
5
+ intersections: Intersection[];
6
+ sets: DataSet[];
7
+ };
8
+ export declare const useSetOverlapChartSizes: ({ width, height, intersections, sets, }: Props) => {
9
+ labels: string[];
10
+ svgWidth: number;
11
+ svgHeight: number;
12
+ intersectionsChartWidth: number;
13
+ intersectionsChartHeight: number;
14
+ matrixY: number;
15
+ matrixHeight: number;
16
+ leftPadding: number;
17
+ setsChartWidth: number;
18
+ };
19
+ export {};
@@ -0,0 +1,35 @@
1
+ import { useMemo } from "react";
2
+ import { GAP_BEFORE_LEFT_BAR, GAP_BETWEEN_TABLE_AND_CHART, MATRIX_ROW_HEIGHT, MAX_SETS_CHART_WIDTH, MIN_INTERSECTION_CHART_HEIGHT, MIN_MATRIX_COLUMN_WIDTH, MIN_SETS_CHART_WIDTH, TEXT_GAP, TOP_GAP, } from "./constants";
3
+ import { truncateLabels } from "./helpers";
4
+ export const useSetOverlapChartSizes = ({ width, height, intersections, sets, }) => useMemo(() => {
5
+ const minMatrixWidth = intersections.length * MIN_MATRIX_COLUMN_WIDTH * 1.2;
6
+ const matrixHeight = sets.length * MATRIX_ROW_HEIGHT;
7
+ const availableLabelsWidth = width -
8
+ minMatrixWidth -
9
+ TEXT_GAP * 2 -
10
+ GAP_BEFORE_LEFT_BAR -
11
+ MIN_SETS_CHART_WIDTH;
12
+ const { labels, maxWidth: labelsWidth } = truncateLabels(sets, availableLabelsWidth);
13
+ const availableSetsChartWidth = width - minMatrixWidth - TEXT_GAP * 2 - GAP_BEFORE_LEFT_BAR - labelsWidth;
14
+ const setsChartWidth = Math.min(Math.max(availableSetsChartWidth, MIN_SETS_CHART_WIDTH), MAX_SETS_CHART_WIDTH);
15
+ const leftPadding = setsChartWidth + labelsWidth + TEXT_GAP * 2 + GAP_BEFORE_LEFT_BAR;
16
+ const intersectionsChartWidth = Math.max(minMatrixWidth, width - leftPadding);
17
+ const intersectionsChartHeight = Math.max(MIN_INTERSECTION_CHART_HEIGHT, height - TOP_GAP - matrixHeight - GAP_BETWEEN_TABLE_AND_CHART);
18
+ const svgWidth = leftPadding + intersectionsChartWidth;
19
+ const svgHeight = intersectionsChartHeight +
20
+ matrixHeight +
21
+ TOP_GAP +
22
+ GAP_BETWEEN_TABLE_AND_CHART;
23
+ const matrixY = svgHeight - matrixHeight - 1;
24
+ return {
25
+ labels,
26
+ svgWidth,
27
+ svgHeight,
28
+ intersectionsChartWidth,
29
+ intersectionsChartHeight,
30
+ matrixY,
31
+ matrixHeight,
32
+ leftPadding,
33
+ setsChartWidth,
34
+ };
35
+ }, [width, height, intersections, sets]);
package/charts/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from "./LineChart";
2
+ export * from "./SetOverlapChart";
package/charts/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from "./LineChart";
2
+ export * from "./SetOverlapChart";
@@ -0,0 +1,5 @@
1
+ import type { AppSelectorProps } from "./AppSelector.types";
2
+ /** Navigation popover for switching between Reltio platform applications.
3
+ * Displays apps grouped by category in a grid layout.
4
+ */
5
+ export declare const AppSelector: ({ apps, env, tenant, className, positionArea, ...rest }: AppSelectorProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "../../components/Button";
3
+ import { Divider } from "../../components/Divider";
4
+ import { Popover } from "../../components/Popover";
5
+ import { Applications } from "../../icons/Applications";
6
+ import { Link } from "../../icons/Link";
7
+ import { classNames } from "../../utils/classNames";
8
+ import styles from "./AppSelector.module.css";
9
+ const DEFAULT_CATEGORY = "Applications";
10
+ /** Navigation popover for switching between Reltio platform applications.
11
+ * Displays apps grouped by category in a grid layout.
12
+ */
13
+ export const AppSelector = ({ apps, env, tenant, className, positionArea = "right span-top", ...rest }) => {
14
+ const validApps = apps.filter((app) => app.name && app.uri);
15
+ const groups = Object.groupBy(validApps, ({ category }) => category || DEFAULT_CATEGORY);
16
+ return (_jsx("nav", { className: classNames(className), "aria-label": "Applications", ...rest, children: _jsx(Popover, { trigger: _jsx(Button, { variant: "text", "aria-label": "Applications", children: _jsx(Applications, {}) }), positionArea: positionArea, children: _jsx("div", { className: classNames(styles.content), children: Object.entries(groups).map(([category, apps]) => (_jsxs("div", { className: classNames(styles.group), children: [_jsx(Divider, { className: classNames(styles.divider), children: category }), _jsx("div", { className: classNames(styles.grid), children: apps?.map((app) => (_jsxs("a", { href: resolveUri(app.uri, env, tenant), target: "_blank", rel: "noopener noreferrer", className: classNames(styles.app), children: [app.icon ? (_jsx("img", { src: app.icon, alt: "", className: classNames(styles.appIcon) })) : (_jsx(Link, { size: "xlarge" })), app.name] }, app.name))) })] }, category))) }) }) }));
17
+ };
18
+ const resolveUri = (uri, env, tenant) => uri
19
+ ?.replaceAll("${environment}", String(env))
20
+ .replaceAll("${tenant}", String(tenant));
@@ -0,0 +1,2 @@
1
+ import json from './AppSelector.module.css.json';
2
+ export default json;
@@ -0,0 +1,61 @@
1
+ import styleInject from 'style-inject';
2
+ import json from './AppSelector.module.css.json';
3
+ styleInject(`
4
+ .AppSelector_content__L29wd {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: 16px;
8
+ padding: 16px;
9
+ width: 316px;
10
+ max-height: 485px;
11
+ overflow-y: auto;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ .AppSelector_group__L29wd {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: 12px;
19
+ }
20
+
21
+ .AppSelector_grid__L29wd {
22
+ display: grid;
23
+ grid-template-columns: repeat(3, 1fr);
24
+ column-gap: 16px;
25
+ row-gap: 24px;
26
+ }
27
+
28
+ .AppSelector_divider__L29wd {
29
+ margin-left: 12px;
30
+ }
31
+
32
+ .AppSelector_app__L29wd {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ gap: 8px;
37
+ padding: 8px 0;
38
+ border-radius: 12px;
39
+ text-align: center;
40
+ font-size: 13px;
41
+ line-height: 1.3;
42
+ color: var(--reltio-color-text);
43
+ text-decoration: none;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .AppSelector_app__L29wd:hover {
48
+ background: var(--reltio-color-bg-transparent-1);
49
+ }
50
+
51
+ .AppSelector_appIcon__L29wd {
52
+ width: 48px;
53
+ height: 48px;
54
+ object-fit: contain;
55
+ }
56
+
57
+ .AppSelector_error__L29wd {
58
+ margin-bottom: -8px;
59
+ }
60
+ `);
61
+ export default json;
@@ -0,0 +1 @@
1
+ { "content": "AppSelector_content__L29wd", "group": "AppSelector_group__L29wd", "grid": "AppSelector_grid__L29wd", "divider": "AppSelector_divider__L29wd", "app": "AppSelector_app__L29wd", "appIcon": "AppSelector_appIcon__L29wd", "error": "AppSelector_error__L29wd" }
@@ -0,0 +1,29 @@
1
+ import type { HtmlProps } from "../../utils/types";
2
+ export type AppEntry = {
3
+ /** Application display name. Apps without a name are ignored. */
4
+ name?: string;
5
+ /** URL opened in a new tab on click. Apps without a URI are ignored. */
6
+ uri?: string;
7
+ /** Absolute URL to the application icon (SVG). Falls back to a generic link icon. */
8
+ icon?: string;
9
+ /** Application category for grouping in navigation.
10
+ * @default "Applications"
11
+ */
12
+ category?: string;
13
+ };
14
+ export type AppSelectorProps = HtmlProps<"div", {
15
+ /** List of apps to display, grouped by category.
16
+ * Apps missing `name` or `uri` are silently ignored.
17
+ * The list of available apps for a given tenant
18
+ * can be retrieved from Reltio Config Service.
19
+ */
20
+ apps: AppEntry[];
21
+ /** Environment identifier substituted into URI templates (`${environment}`). */
22
+ env?: string;
23
+ /** Tenant identifier substituted into URI templates (`${tenant}`). */
24
+ tenant?: string;
25
+ /** CSS `position-area` value controlling popover placement relative to the trigger.
26
+ * @default "right span-top"
27
+ */
28
+ positionArea?: string;
29
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./AppSelector";
2
+ export type * from "./AppSelector.types";
@@ -0,0 +1 @@
1
+ export * from "./AppSelector";
@@ -5,5 +5,8 @@ import type { ButtonProps } from "./Button.types";
5
5
  * A flexible, accessible button component that supports multiple variants
6
6
  * (filled, outlined, text), color options (primary, secondary, inherited),
7
7
  * sizes, states, and can render as either a button or anchor element.
8
+ *
9
+ * Automatically switches to circular icon-only layout when children
10
+ * is a single React component element.
8
11
  */
9
12
  export declare const Button: ({ variant, color, size, disabled, fullWidth, children, className, href, type, ...rest }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useRef } from "react";
2
+ import React, { useEffect, useRef } from "react";
3
3
  import { classNames } from "../../utils/classNames";
4
4
  import styles from "./Button.module.css";
5
5
  /**
@@ -8,6 +8,9 @@ import styles from "./Button.module.css";
8
8
  * A flexible, accessible button component that supports multiple variants
9
9
  * (filled, outlined, text), color options (primary, secondary, inherited),
10
10
  * sizes, states, and can render as either a button or anchor element.
11
+ *
12
+ * Automatically switches to circular icon-only layout when children
13
+ * is a single React component element.
11
14
  */
12
15
  export const Button = ({ variant = "filled", color = "inherited", size = "medium", disabled = false, fullWidth = false, children, className, href, type = "button", ...rest }) => {
13
16
  const buttonRef = useRef(null);
@@ -19,8 +22,11 @@ export const Button = ({ variant = "filled", color = "inherited", size = "medium
19
22
  buttonRef.current.blur();
20
23
  }
21
24
  }, [disabled]);
22
- // Compose className using classNames utility
23
- const composedClassName = classNames(styles.root, styles[variant], styles[color], styles[size], disabled && styles.disabled, fullWidth && styles.fullWidth, className);
25
+ const isIconOnly = React.Children.count(children) === 1 &&
26
+ React.isValidElement(children) &&
27
+ typeof children.type !== "string" &&
28
+ children.type !== React.Fragment;
29
+ const composedClassName = classNames(styles.root, styles[variant], styles[color], styles[size], disabled && styles.disabled, fullWidth && styles.fullWidth, isIconOnly && styles.iconOnly, className);
24
30
  // Render as anchor if href is provided
25
31
  if (href) {
26
32
  return (_jsx("a", { ref: buttonRef, href: disabled ? undefined : href, className: composedClassName, "aria-disabled": disabled ? true : undefined, ...rest, children: children }));
@@ -115,20 +115,20 @@ styleInject(`
115
115
  /* Size: Small */
116
116
  .Button_small__L29wd {
117
117
  min-height: 32px;
118
- padding: 8px 16px;
118
+ padding: 8px 12px;
119
119
  font-size: 0.875rem;
120
120
  }
121
121
 
122
122
  /* Size: Medium */
123
123
  .Button_medium__L29wd {
124
124
  min-height: 40px;
125
- padding: 12px 20px;
125
+ padding: 12px 18px;
126
126
  }
127
127
 
128
128
  /* Size: Large */
129
129
  .Button_large__L29wd {
130
130
  min-height: 48px;
131
- padding: 16px 32px;
131
+ padding: 16px 24px;
132
132
  }
133
133
 
134
134
  /* State: Disabled */
@@ -137,6 +137,12 @@ styleInject(`
137
137
  cursor: not-allowed;
138
138
  }
139
139
 
140
+ /* Icon Only */
141
+ .Button_iconOnly__L29wd {
142
+ aspect-ratio: 1;
143
+ padding: 0;
144
+ }
145
+
140
146
  /* Full Width */
141
147
  .Button_fullWidth__L29wd {
142
148
  width: 100%;
@@ -1 +1 @@
1
- { "root": "Button_root__L29wd", "filled": "Button_filled__L29wd", "outlined": "Button_outlined__L29wd", "text": "Button_text__L29wd", "primary": "Button_primary__L29wd", "inherited": "Button_inherited__L29wd", "small": "Button_small__L29wd", "medium": "Button_medium__L29wd", "large": "Button_large__L29wd", "disabled": "Button_disabled__L29wd", "fullWidth": "Button_fullWidth__L29wd" }
1
+ { "root": "Button_root__L29wd", "filled": "Button_filled__L29wd", "outlined": "Button_outlined__L29wd", "text": "Button_text__L29wd", "primary": "Button_primary__L29wd", "inherited": "Button_inherited__L29wd", "small": "Button_small__L29wd", "medium": "Button_medium__L29wd", "large": "Button_large__L29wd", "disabled": "Button_disabled__L29wd", "iconOnly": "Button_iconOnly__L29wd", "fullWidth": "Button_fullWidth__L29wd" }
@@ -0,0 +1,2 @@
1
+ import type { DividerProps } from "./Divider.types";
2
+ export declare const Divider: ({ align, children, className, ...rest }: DividerProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { classNames } from "../../utils/classNames";
3
+ import styles from "./Divider.module.css";
4
+ export const Divider = ({ align = "start", children, className, ...rest }) => {
5
+ return (_jsx("div", { role: "separator", className: classNames(styles.root, children ? styles.labeled : styles.plain, children ? styles[align] : undefined, className), ...rest, children: children }));
6
+ };
@@ -0,0 +1,2 @@
1
+ import json from './Divider.module.css.json';
2
+ export default json;