@foodpilot/foods 2.8.0 → 2.9.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.
@@ -0,0 +1,35 @@
1
+ import { ReactNode } from 'react';
2
+ export type BreakdownNode = {
3
+ label: string;
4
+ value: number;
5
+ children?: BreakdownNode[];
6
+ };
7
+ export type BreakdownTableTextOptions = {
8
+ categoryHeader?: string;
9
+ scoreHeader?: string;
10
+ total?: string;
11
+ };
12
+ export type BreakdownTableProps = {
13
+ groups: BreakdownNode[];
14
+ valuesFormatterFn?: (value: number, precision?: number) => string;
15
+ valuesUnit?: string;
16
+ isLoading?: boolean;
17
+ headerLeft?: ReactNode;
18
+ headerRight?: ReactNode;
19
+ textOptions?: BreakdownTableTextOptions;
20
+ enableSorting?: boolean;
21
+ manualSort?: boolean;
22
+ sorting?: {
23
+ column: "label" | "value" | null;
24
+ direction: "asc" | "desc";
25
+ };
26
+ onSortingChange?: (s: {
27
+ column: "label" | "value" | null;
28
+ direction: "asc" | "desc";
29
+ }) => void;
30
+ resizable?: boolean;
31
+ initialRightColWidth?: number;
32
+ minRightColWidth?: number;
33
+ maxRightColWidth?: number;
34
+ };
35
+ export declare const BreakdownTable: (props: BreakdownTableProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,137 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useMemo } from "react";
3
+ import { useTheme, Box, TableContainer, Table, TableBody, TableRow, TableCell, Typography, Stack } from "@mui/material";
4
+ import { BreakdownTableRow } from "./BreakdownTableRow.js";
5
+ import { useBreakdownSorting } from "./useBreakdownSorting.js";
6
+ import { useResizableRightColumn } from "./useResizableRightColumn.js";
7
+ import { BreakdownTableSkeleton } from "./BreakdownTableSkeleton.js";
8
+ import { BreakdownTableEmpty } from "./BreakdownTableEmpty.js";
9
+ import { BreakdownTableHeader } from "./BreakdownTableHeader.js";
10
+ const BreakdownTable = (props) => {
11
+ const theme = useTheme();
12
+ const {
13
+ groups,
14
+ valuesFormatterFn = (v, p) => theme.numbers.valueFormatterFn(v, p),
15
+ valuesUnit,
16
+ isLoading,
17
+ textOptions
18
+ } = props;
19
+ const texts = { ...textOptions ?? {} };
20
+ const [expanded, setExpanded] = useState({});
21
+ const { sortingState, sortingEnabled, groupsToRender, toggleSort } = useBreakdownSorting({
22
+ groups,
23
+ sorting: props.sorting,
24
+ enableSorting: props.enableSorting,
25
+ manualSort: props.manualSort,
26
+ onSortingChange: props.onSortingChange
27
+ });
28
+ const { rightColWidth, onMouseDownResizer } = useResizableRightColumn({
29
+ resizable: props.resizable,
30
+ initialRightColWidth: props.initialRightColWidth,
31
+ minRightColWidth: props.minRightColWidth,
32
+ maxRightColWidth: props.maxRightColWidth
33
+ });
34
+ const total = useMemo(() => groups.reduce((acc, g) => acc + (Number(g.value) || 0), 0), [groups]);
35
+ const getRowCellSx = (level) => ({
36
+ borderBottom: level === 0 ? `1px solid ${theme.palette.divider}` : "none",
37
+ padding: theme.spacing(2, 3)
38
+ });
39
+ const indentForLevel = (level) => level > 0 ? theme.spacing(6 + (level - 1) * 4.5) : void 0;
40
+ if (isLoading) {
41
+ const rows = Math.max(5, (groups == null ? void 0 : groups.length) || 6);
42
+ return /* @__PURE__ */ jsx(BreakdownTableSkeleton, { rows });
43
+ }
44
+ const isEmpty = ((groups == null ? void 0 : groups.length) ?? 0) === 0;
45
+ if (isEmpty) {
46
+ return /* @__PURE__ */ jsx(
47
+ BreakdownTableEmpty,
48
+ {
49
+ rightColWidth,
50
+ texts,
51
+ headerLeft: props.headerLeft,
52
+ headerRight: props.headerRight,
53
+ sortingEnabled,
54
+ sortingState,
55
+ onToggleSort: toggleSort,
56
+ resizable: props.resizable ?? true,
57
+ onMouseDownResizer
58
+ }
59
+ );
60
+ }
61
+ return /* @__PURE__ */ jsx(
62
+ Box,
63
+ {
64
+ position: "relative",
65
+ width: "100%",
66
+ sx: {
67
+ border: `1px solid ${theme.palette.divider}`,
68
+ borderRadius: theme.shape.borderRadius,
69
+ backgroundColor: theme.palette.background.paper
70
+ },
71
+ children: /* @__PURE__ */ jsx(TableContainer, { sx: { borderRadius: theme.shape.borderRadius }, children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs(TableBody, { children: [
72
+ /* @__PURE__ */ jsx(
73
+ BreakdownTableHeader,
74
+ {
75
+ rightColWidth,
76
+ sortingEnabled,
77
+ sortingState,
78
+ onToggleSort: toggleSort,
79
+ resizable: props.resizable ?? true,
80
+ onMouseDownResizer,
81
+ headerLeft: props.headerLeft,
82
+ headerRight: props.headerRight,
83
+ texts
84
+ }
85
+ ),
86
+ groupsToRender.map((g) => /* @__PURE__ */ jsx(
87
+ BreakdownTableRow,
88
+ {
89
+ node: g,
90
+ level: 0,
91
+ path: g.label,
92
+ valuesFormatterFn,
93
+ valuesUnit,
94
+ expanded,
95
+ setExpanded,
96
+ getRowCellSx,
97
+ indentForLevel
98
+ },
99
+ `row-${g.label}`
100
+ )),
101
+ /* @__PURE__ */ jsxs(TableRow, { children: [
102
+ /* @__PURE__ */ jsx(
103
+ TableCell,
104
+ {
105
+ sx: {
106
+ ...getRowCellSx(0),
107
+ borderBottom: 0,
108
+ borderRight: `1px solid ${theme.palette.divider}`
109
+ },
110
+ children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 700, color: theme.palette.text.primary }, children: texts.total })
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsx(
114
+ TableCell,
115
+ {
116
+ sx: {
117
+ ...getRowCellSx(0),
118
+ borderBottom: 0,
119
+ width: `${rightColWidth}px`,
120
+ minWidth: `${rightColWidth}px`,
121
+ maxWidth: `${rightColWidth}px`
122
+ },
123
+ align: "right",
124
+ children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", justifyContent: "flex-end", children: [
125
+ /* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 700 }, children: valuesFormatterFn(total) }),
126
+ valuesUnit && /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: theme.palette.text.secondary }, children: valuesUnit })
127
+ ] })
128
+ }
129
+ )
130
+ ] })
131
+ ] }) }) })
132
+ }
133
+ );
134
+ };
135
+ export {
136
+ BreakdownTable
137
+ };
@@ -0,0 +1,14 @@
1
+ import { BreakdownTableTextOptions } from './BreakdownTable';
2
+ import { SortingState } from './useBreakdownSorting';
3
+ export type BreakdownTableEmptyProps = {
4
+ rightColWidth: number;
5
+ texts: BreakdownTableTextOptions;
6
+ headerLeft?: React.ReactNode;
7
+ headerRight?: React.ReactNode;
8
+ sortingEnabled: boolean;
9
+ sortingState: SortingState;
10
+ onToggleSort: (column: "label" | "value") => void;
11
+ resizable?: boolean;
12
+ onMouseDownResizer: (e: React.MouseEvent) => void;
13
+ };
14
+ export declare const BreakdownTableEmpty: (props: BreakdownTableEmptyProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,89 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useTheme, Box, TableContainer, Table, TableBody, TableRow, TableCell, Stack, Typography } from "@mui/material";
3
+ import { FoodsBadge } from "../../Badge/FoodsBadge.js";
4
+ import { BreakdownTableHeader } from "./BreakdownTableHeader.js";
5
+ const BreakdownTableEmpty = (props) => {
6
+ const theme = useTheme();
7
+ const {
8
+ rightColWidth,
9
+ texts,
10
+ headerLeft,
11
+ headerRight,
12
+ sortingEnabled,
13
+ sortingState,
14
+ onToggleSort,
15
+ resizable = true,
16
+ onMouseDownResizer
17
+ } = props;
18
+ return /* @__PURE__ */ jsx(
19
+ Box,
20
+ {
21
+ position: "relative",
22
+ width: "100%",
23
+ sx: {
24
+ border: `1px solid ${theme.palette.divider}`,
25
+ borderRadius: theme.shape.borderRadius,
26
+ backgroundColor: theme.palette.background.paper
27
+ },
28
+ children: /* @__PURE__ */ jsx(TableContainer, { sx: { borderRadius: theme.shape.borderRadius }, children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs(TableBody, { children: [
29
+ /* @__PURE__ */ jsx(
30
+ BreakdownTableHeader,
31
+ {
32
+ rightColWidth,
33
+ sortingEnabled,
34
+ sortingState,
35
+ onToggleSort,
36
+ resizable,
37
+ onMouseDownResizer,
38
+ headerLeft,
39
+ headerRight,
40
+ texts
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsxs(TableRow, { children: [
44
+ /* @__PURE__ */ jsx(
45
+ TableCell,
46
+ {
47
+ sx: {
48
+ padding: theme.spacing(2),
49
+ borderBottom: 0,
50
+ borderRight: `1px solid ${theme.palette.divider}`
51
+ },
52
+ children: /* @__PURE__ */ jsx(Stack, { direction: "row", alignItems: "center", spacing: 2, children: /* @__PURE__ */ jsx(
53
+ FoodsBadge,
54
+ {
55
+ size: 4,
56
+ icon: "empty",
57
+ boxProps: {
58
+ sx: {
59
+ borderRadius: "3px",
60
+ backgroundColor: theme.custom.grey[400],
61
+ color: theme.custom.grey[1400]
62
+ }
63
+ }
64
+ }
65
+ ) })
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsx(
69
+ TableCell,
70
+ {
71
+ sx: {
72
+ padding: theme.spacing(2),
73
+ borderBottom: 0,
74
+ width: `${rightColWidth}px`,
75
+ minWidth: `${rightColWidth}px`,
76
+ maxWidth: `${rightColWidth}px`
77
+ },
78
+ align: "right",
79
+ children: /* @__PURE__ */ jsx(Typography, { component: "span", children: "—" })
80
+ }
81
+ )
82
+ ] })
83
+ ] }) }) })
84
+ }
85
+ );
86
+ };
87
+ export {
88
+ BreakdownTableEmpty
89
+ };
@@ -0,0 +1,14 @@
1
+ import { BreakdownTableTextOptions } from './BreakdownTable';
2
+ import { SortingState } from './useBreakdownSorting';
3
+ export type BreakdownTableHeaderProps = {
4
+ rightColWidth: number;
5
+ sortingEnabled: boolean;
6
+ sortingState: SortingState;
7
+ onToggleSort: (column: "label" | "value") => void;
8
+ resizable?: boolean;
9
+ onMouseDownResizer: (e: React.MouseEvent) => void;
10
+ headerLeft?: React.ReactNode;
11
+ headerRight?: React.ReactNode;
12
+ texts: BreakdownTableTextOptions;
13
+ };
14
+ export declare const BreakdownTableHeader: (props: BreakdownTableHeaderProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,85 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useTheme, TableRow, TableCell, Stack, Typography, Box } from "@mui/material";
3
+ import { FoodsIcon } from "../../Icons/FoodsIcon.js";
4
+ const BreakdownTableHeader = (props) => {
5
+ const theme = useTheme();
6
+ const {
7
+ rightColWidth,
8
+ sortingEnabled,
9
+ sortingState,
10
+ onToggleSort,
11
+ resizable = true,
12
+ onMouseDownResizer,
13
+ headerLeft,
14
+ headerRight,
15
+ texts
16
+ } = props;
17
+ const headerCellSx = {
18
+ backgroundColor: theme.palette.grey[200],
19
+ borderBottom: `1px solid ${theme.palette.divider}`,
20
+ py: theme.spacing(3),
21
+ px: theme.spacing(3),
22
+ color: theme.palette.text.primary
23
+ };
24
+ return /* @__PURE__ */ jsxs(TableRow, { children: [
25
+ /* @__PURE__ */ jsx(TableCell, { sx: { ...headerCellSx, borderRight: `1px solid ${theme.palette.divider}` }, children: headerLeft ?? /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", children: [
26
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 500, color: theme.palette.text.primary }, children: texts.categoryHeader }),
27
+ sortingEnabled && /* @__PURE__ */ jsx(
28
+ Box,
29
+ {
30
+ onClick: () => onToggleSort("label"),
31
+ sx: { display: "flex", alignItems: "center", cursor: "pointer", p: theme.spacing(0.5) },
32
+ children: /* @__PURE__ */ jsx(
33
+ FoodsIcon,
34
+ {
35
+ size: 2,
36
+ icon: sortingState.column === "label" ? sortingState.direction === "asc" ? "sortAscending" : "sortDescending" : "sortNoSorting"
37
+ }
38
+ )
39
+ }
40
+ )
41
+ ] }) }),
42
+ /* @__PURE__ */ jsxs(
43
+ TableCell,
44
+ {
45
+ sx: {
46
+ ...headerCellSx,
47
+ width: `${rightColWidth}px`,
48
+ minWidth: `${rightColWidth}px`,
49
+ maxWidth: `${rightColWidth}px`,
50
+ position: "relative"
51
+ },
52
+ align: "right",
53
+ children: [
54
+ headerRight ?? /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", justifyContent: "flex-end", spacing: 1, children: [
55
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 500, color: theme.palette.text.primary }, children: texts.scoreHeader }),
56
+ sortingEnabled && /* @__PURE__ */ jsx(
57
+ Box,
58
+ {
59
+ onClick: () => onToggleSort("value"),
60
+ sx: { display: "flex", alignItems: "center", cursor: "pointer", p: theme.spacing(0.5) },
61
+ children: /* @__PURE__ */ jsx(
62
+ FoodsIcon,
63
+ {
64
+ size: 2,
65
+ icon: sortingState.column === "value" ? sortingState.direction === "asc" ? "sortAscending" : "sortDescending" : "sortNoSorting"
66
+ }
67
+ )
68
+ }
69
+ )
70
+ ] }),
71
+ resizable && /* @__PURE__ */ jsx(
72
+ Box,
73
+ {
74
+ onMouseDown: onMouseDownResizer,
75
+ sx: { position: "absolute", left: -4, top: 0, bottom: 0, width: 8, cursor: "col-resize" }
76
+ }
77
+ )
78
+ ]
79
+ }
80
+ )
81
+ ] });
82
+ };
83
+ export {
84
+ BreakdownTableHeader
85
+ };
@@ -0,0 +1,16 @@
1
+ import { Theme } from '@mui/material';
2
+ import { SystemStyleObject } from '@mui/system';
3
+ import { BreakdownNode } from './BreakdownTable';
4
+ import { Dispatch, SetStateAction } from 'react';
5
+ export type BreakdownTableRowProps = {
6
+ node: BreakdownNode;
7
+ level: number;
8
+ path: string;
9
+ valuesFormatterFn: (value: number, precision?: number) => string;
10
+ valuesUnit?: string;
11
+ expanded: Record<string, boolean>;
12
+ setExpanded: Dispatch<SetStateAction<Record<string, boolean>>>;
13
+ getRowCellSx: (level: number) => SystemStyleObject<Theme>;
14
+ indentForLevel: (level: number) => string | undefined;
15
+ };
16
+ export declare const BreakdownTableRow: (props: BreakdownTableRowProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,90 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useTheme, TableRow, TableCell, Stack, Box, Typography } from "@mui/material";
3
+ import { FoodsIcon } from "../../Icons/FoodsIcon.js";
4
+ const BreakdownTableRow = (props) => {
5
+ var _a;
6
+ const theme = useTheme();
7
+ const { node, level, path, valuesFormatterFn, valuesUnit, expanded, setExpanded, getRowCellSx, indentForLevel } = props;
8
+ const isExpanded = expanded[path] === true;
9
+ const hasChildren = !!((_a = node.children) == null ? void 0 : _a.length);
10
+ const borderBottomStyle = level === 0 && !isExpanded ? `1px solid ${theme.palette.divider}` : "none";
11
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
12
+ /* @__PURE__ */ jsxs(TableRow, { children: [
13
+ /* @__PURE__ */ jsx(
14
+ TableCell,
15
+ {
16
+ sx: {
17
+ ...getRowCellSx(level),
18
+ borderBottom: borderBottomStyle,
19
+ borderRight: `1px solid ${theme.palette.divider}`,
20
+ pl: indentForLevel(level)
21
+ },
22
+ children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1.5, alignItems: "center", children: [
23
+ hasChildren ? /* @__PURE__ */ jsx(
24
+ Box,
25
+ {
26
+ component: "button",
27
+ "aria-label": isExpanded ? "collapse" : "expand",
28
+ onClick: () => setExpanded((e) => ({ ...e, [path]: !isExpanded })),
29
+ sx: {
30
+ width: theme.spacing(3),
31
+ height: theme.spacing(3),
32
+ display: "flex",
33
+ alignItems: "center",
34
+ justifyContent: "center",
35
+ borderRadius: theme.spacing(3.75),
36
+ border: `1px solid ${theme.palette.grey[300]}`,
37
+ backgroundColor: theme.palette.background.paper,
38
+ cursor: "pointer",
39
+ padding: 0,
40
+ outline: "none",
41
+ borderColor: theme.palette.grey[300],
42
+ "&:focus-visible": {
43
+ boxShadow: `0 0 0 2px ${theme.palette.primary.light}`
44
+ }
45
+ },
46
+ children: /* @__PURE__ */ jsx(FoodsIcon, { size: 2, icon: isExpanded ? "arrowUpShort" : "arrowRightShort" })
47
+ }
48
+ ) : /* @__PURE__ */ jsx(Fragment, {}),
49
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 700, color: theme.palette.text.primary }, children: node.label })
50
+ ] })
51
+ }
52
+ ),
53
+ /* @__PURE__ */ jsx(TableCell, { sx: { ...getRowCellSx(level), borderBottom: borderBottomStyle }, align: "right", children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", justifyContent: "flex-end", children: [
54
+ /* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 700 }, children: valuesFormatterFn(node.value) }),
55
+ valuesUnit && /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: theme.palette.text.secondary }, children: valuesUnit })
56
+ ] }) })
57
+ ] }, `row-${path}`),
58
+ hasChildren && isExpanded && node.children.map((child) => /* @__PURE__ */ jsx(
59
+ BreakdownTableRow,
60
+ {
61
+ node: child,
62
+ level: level + 1,
63
+ path: `${path} > ${child.label}`,
64
+ valuesFormatterFn,
65
+ valuesUnit,
66
+ expanded,
67
+ setExpanded,
68
+ getRowCellSx,
69
+ indentForLevel
70
+ },
71
+ `row-${path} > ${child.label}`
72
+ )),
73
+ level === 0 && hasChildren && isExpanded && /* @__PURE__ */ jsxs(TableRow, { children: [
74
+ /* @__PURE__ */ jsx(
75
+ TableCell,
76
+ {
77
+ sx: {
78
+ padding: 0,
79
+ borderBottom: `1px solid ${theme.palette.divider}`,
80
+ borderRight: `1px solid ${theme.palette.divider}`
81
+ }
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(TableCell, { sx: { padding: 0, borderBottom: `1px solid ${theme.palette.divider}` } })
85
+ ] }, `separator-${path}`)
86
+ ] });
87
+ };
88
+ export {
89
+ BreakdownTableRow
90
+ };
@@ -0,0 +1,3 @@
1
+ export declare const BreakdownTableSkeleton: ({ rows }: {
2
+ rows: number;
3
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,58 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useTheme, Box, TableContainer, Table, TableBody, TableRow, TableCell, Skeleton, Stack } from "@mui/material";
3
+ const BreakdownTableSkeleton = ({ rows }) => {
4
+ const theme = useTheme();
5
+ const headerCellSx = {
6
+ backgroundColor: theme.palette.grey[200],
7
+ borderBottom: `1px solid ${theme.palette.divider}`,
8
+ py: theme.spacing(3),
9
+ px: theme.spacing(3),
10
+ color: theme.palette.text.primary
11
+ };
12
+ const getRowCellSx = {
13
+ borderBottom: `1px solid ${theme.palette.divider}`,
14
+ padding: theme.spacing(2, 3)
15
+ };
16
+ return /* @__PURE__ */ jsx(
17
+ Box,
18
+ {
19
+ position: "relative",
20
+ width: "100%",
21
+ sx: {
22
+ border: `1px solid ${theme.palette.divider}`,
23
+ borderRadius: theme.shape.borderRadius,
24
+ backgroundColor: theme.palette.background.paper
25
+ },
26
+ children: /* @__PURE__ */ jsx(TableContainer, { sx: { borderRadius: theme.shape.borderRadius }, children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs(TableBody, { children: [
27
+ /* @__PURE__ */ jsxs(TableRow, { children: [
28
+ /* @__PURE__ */ jsx(
29
+ TableCell,
30
+ {
31
+ sx: { ...headerCellSx, borderRight: `1px solid ${theme.palette.divider}` },
32
+ width: "auto",
33
+ children: /* @__PURE__ */ jsx(Skeleton, { width: 120, height: 18 })
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx(TableCell, { sx: { ...headerCellSx, width: theme.spacing(25) }, align: "right", children: /* @__PURE__ */ jsx(Skeleton, { width: 80, height: 18, sx: { ml: "auto" } }) })
37
+ ] }),
38
+ Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
39
+ /* @__PURE__ */ jsx(TableCell, { sx: { ...getRowCellSx, borderRight: `1px solid ${theme.palette.divider}` }, children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 2, alignItems: "center", children: [
40
+ /* @__PURE__ */ jsx(
41
+ Skeleton,
42
+ {
43
+ variant: "circular",
44
+ width: theme.spacing(3),
45
+ height: theme.spacing(3)
46
+ }
47
+ ),
48
+ /* @__PURE__ */ jsx(Skeleton, { width: "60%", height: 18 })
49
+ ] }) }),
50
+ /* @__PURE__ */ jsx(TableCell, { sx: getRowCellSx, align: "right", children: /* @__PURE__ */ jsx(Skeleton, { width: 100, height: 18, sx: { ml: "auto" } }) })
51
+ ] }, `breakdown-table-skeleton-${i}`))
52
+ ] }) }) })
53
+ }
54
+ );
55
+ };
56
+ export {
57
+ BreakdownTableSkeleton
58
+ };
@@ -0,0 +1,19 @@
1
+ import { BreakdownNode } from './BreakdownTable';
2
+ export type SortingState = {
3
+ column: "label" | "value" | null;
4
+ direction: "asc" | "desc";
5
+ };
6
+ type Params = {
7
+ groups: BreakdownNode[];
8
+ sorting?: SortingState;
9
+ enableSorting?: boolean;
10
+ manualSort?: boolean;
11
+ onSortingChange?: (s: SortingState) => void;
12
+ };
13
+ export declare const useBreakdownSorting: ({ groups, sorting, enableSorting, manualSort, onSortingChange, }: Params) => {
14
+ readonly sortingState: SortingState;
15
+ readonly sortingEnabled: boolean;
16
+ readonly groupsToRender: BreakdownNode[];
17
+ readonly toggleSort: (column: "label" | "value") => void;
18
+ };
19
+ export {};
@@ -0,0 +1,40 @@
1
+ import { useState, useMemo } from "react";
2
+ const useBreakdownSorting = ({
3
+ groups,
4
+ sorting,
5
+ enableSorting = true,
6
+ manualSort = false,
7
+ onSortingChange
8
+ }) => {
9
+ const [internalSorting, setInternalSorting] = useState({ column: null, direction: "asc" });
10
+ const sortingState = sorting ?? internalSorting;
11
+ const sortingEnabled = enableSorting;
12
+ const setSorting = (s) => {
13
+ onSortingChange == null ? void 0 : onSortingChange(s);
14
+ setInternalSorting(s);
15
+ };
16
+ const toggleSort = (column) => {
17
+ if (sortingEnabled) {
18
+ const next = sortingState.column !== column ? { column, direction: "asc" } : { column, direction: sortingState.direction === "asc" ? "desc" : "asc" };
19
+ setSorting(next);
20
+ }
21
+ };
22
+ const sortNodes = (nodes) => {
23
+ if (sortingState.column) {
24
+ const arr = [...nodes].sort((a, b) => {
25
+ if (sortingState.column === "label") return a.label.localeCompare(b.label);
26
+ return (a.value || 0) - (b.value || 0);
27
+ });
28
+ if (sortingState.direction === "desc") arr.reverse();
29
+ return arr.map((n) => ({ ...n, children: n.children ? sortNodes(n.children) : void 0 }));
30
+ }
31
+ return nodes;
32
+ };
33
+ const groupsToRender = useMemo(() => {
34
+ return !manualSort && sortingState.column ? sortNodes(groups) : groups;
35
+ }, [groups, manualSort, sortingState]);
36
+ return { sortingState, sortingEnabled, groupsToRender, toggleSort };
37
+ };
38
+ export {
39
+ useBreakdownSorting
40
+ };
@@ -0,0 +1,10 @@
1
+ export type ResizableParams = {
2
+ resizable?: boolean;
3
+ initialRightColWidth?: number;
4
+ minRightColWidth?: number;
5
+ maxRightColWidth?: number;
6
+ };
7
+ export declare const useResizableRightColumn: ({ resizable, initialRightColWidth, minRightColWidth, maxRightColWidth, }: ResizableParams) => {
8
+ readonly rightColWidth: number;
9
+ readonly onMouseDownResizer: (e: React.MouseEvent) => void;
10
+ };
@@ -0,0 +1,42 @@
1
+ import { useState, useRef } from "react";
2
+ import { useTheme } from "@mui/material";
3
+ const useResizableRightColumn = ({
4
+ resizable = true,
5
+ initialRightColWidth,
6
+ minRightColWidth,
7
+ maxRightColWidth
8
+ }) => {
9
+ const theme = useTheme();
10
+ const defaultRightWidth = Number.parseInt(String(theme.spacing(25)).replace("px", "")) || 200;
11
+ const minW = minRightColWidth ?? 200;
12
+ const maxW = maxRightColWidth ?? 460;
13
+ const [rightColWidth, setRightColWidth] = useState(initialRightColWidth ?? defaultRightWidth);
14
+ const isResizing = useRef(false);
15
+ const startX = useRef(0);
16
+ const startWidth = useRef(0);
17
+ const onMouseDownResizer = (e) => {
18
+ if (!resizable) return;
19
+ isResizing.current = true;
20
+ startX.current = e.clientX;
21
+ startWidth.current = rightColWidth;
22
+ const handleMove = (ev) => {
23
+ if (!isResizing.current) return;
24
+ const dx = ev.clientX - startX.current;
25
+ const next = Math.max(minW, Math.min(maxW, startWidth.current - dx));
26
+ setRightColWidth(next);
27
+ };
28
+ const handleUp = () => {
29
+ if (!isResizing.current) return;
30
+ isResizing.current = false;
31
+ document.removeEventListener("mousemove", handleMove);
32
+ document.removeEventListener("mouseup", handleUp);
33
+ };
34
+ document.addEventListener("mousemove", handleMove);
35
+ document.addEventListener("mouseup", handleUp);
36
+ e.preventDefault();
37
+ };
38
+ return { rightColWidth, onMouseDownResizer };
39
+ };
40
+ export {
41
+ useResizableRightColumn
42
+ };
@@ -233,6 +233,7 @@ const PolymorphicCumulativeChart = (props) => {
233
233
  placement: "top",
234
234
  popperZIndex: 2e3,
235
235
  width: "unset",
236
+ sx: { maxWidth: "300px" },
236
237
  children: /* @__PURE__ */ jsx(
237
238
  Box,
238
239
  {
@@ -6,3 +6,4 @@ export * from './HorizontalGauge/HorizontalGaugeChart';
6
6
  export * from './TrajectoryChart/TrajectoryChart';
7
7
  export * from './HistogramComparisonChart';
8
8
  export * from './PolymorphicChart';
9
+ export * from './BreakdownTable/BreakdownTable';
package/dist/main.js CHANGED
@@ -43,6 +43,7 @@ import { CustomizedAxisTick } from "./components/Chart/HistogramComparisonChart/
43
43
  import { HistogramComparisonChart } from "./components/Chart/HistogramComparisonChart/HistogramComparisonChart.js";
44
44
  import { PolymorphicChart } from "./components/Chart/PolymorphicChart/PolymorphicChart.js";
45
45
  import { PolymorphicChartRenderer } from "./components/Chart/PolymorphicChart/PolymorphicChartRenderer.js";
46
+ import { BreakdownTable } from "./components/Chart/BreakdownTable/BreakdownTable.js";
46
47
  import { PrimaryCheckbox } from "./components/Checkbox/PrimaryCheckbox.js";
47
48
  import { anyToCheckbox, checkboxToAny } from "./components/Checkbox/helperFunctions.js";
48
49
  import { FoodsCheckbox } from "./components/Checkbox/FoodsCheckbox.js";
@@ -157,6 +158,7 @@ export {
157
158
  BlockListContainer,
158
159
  BottomSheetDialog,
159
160
  BoxStyled,
161
+ BreakdownTable,
160
162
  BredTheme,
161
163
  BredThemeOptions,
162
164
  Button,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@foodpilot/foods",
3
3
  "private": false,
4
- "version": "2.8.0",
4
+ "version": "2.9.0",
5
5
  "type": "module",
6
6
  "main": "./dist/main.js",
7
7
  "module": "./dist/main.js",