@foodpilot/foods 2.8.0 → 2.10.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 (27) hide show
  1. package/dist/components/Chart/BreakdownTable/BreakdownTable.d.ts +35 -0
  2. package/dist/components/Chart/BreakdownTable/BreakdownTable.js +137 -0
  3. package/dist/components/Chart/BreakdownTable/BreakdownTableEmpty.d.ts +14 -0
  4. package/dist/components/Chart/BreakdownTable/BreakdownTableEmpty.js +89 -0
  5. package/dist/components/Chart/BreakdownTable/BreakdownTableHeader.d.ts +14 -0
  6. package/dist/components/Chart/BreakdownTable/BreakdownTableHeader.js +85 -0
  7. package/dist/components/Chart/BreakdownTable/BreakdownTableRow.d.ts +16 -0
  8. package/dist/components/Chart/BreakdownTable/BreakdownTableRow.js +90 -0
  9. package/dist/components/Chart/BreakdownTable/BreakdownTableSkeleton.d.ts +3 -0
  10. package/dist/components/Chart/BreakdownTable/BreakdownTableSkeleton.js +58 -0
  11. package/dist/components/Chart/BreakdownTable/useBreakdownSorting.d.ts +19 -0
  12. package/dist/components/Chart/BreakdownTable/useBreakdownSorting.js +40 -0
  13. package/dist/components/Chart/BreakdownTable/useResizableRightColumn.d.ts +10 -0
  14. package/dist/components/Chart/BreakdownTable/useResizableRightColumn.js +42 -0
  15. package/dist/components/Chart/PolymorphicChart/charts/PolymorphicCumulativeChart.js +1 -0
  16. package/dist/components/Chart/index.d.ts +1 -0
  17. package/dist/components/Icons/FoodsIcon.d.ts +2 -1
  18. package/dist/components/Tabs/FoodsTabs/FoodsTabTemplate.d.ts +9 -0
  19. package/dist/components/Tabs/FoodsTabs/FoodsTabs.d.ts +35 -0
  20. package/dist/main.js +6 -0
  21. package/dist/themes/index.d.ts +1 -0
  22. package/dist/themes/responsive/index.d.ts +2 -0
  23. package/dist/themes/responsive/useResponsiveBreakpoints.d.ts +6 -0
  24. package/dist/themes/responsive/useResponsiveBreakpoints.js +18 -0
  25. package/dist/themes/responsive/useResponsivePadding.d.ts +1 -0
  26. package/dist/themes/responsive/useResponsivePadding.js +12 -0
  27. package/package.json +1 -1
@@ -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';
@@ -1,9 +1,10 @@
1
1
  import { WidthHeight, iconMapping } from './iconConfig';
2
2
  export type IconMapping = typeof iconMapping;
3
+ export type IconMappingProps = keyof IconMapping | (string & {}) | undefined;
3
4
  type Size = WidthHeight | number;
4
5
  export type FoodsIconProps = {
5
6
  size: Size;
6
- icon: keyof IconMapping | (string & {}) | undefined;
7
+ icon: IconMappingProps;
7
8
  placeholder?: "empty" | "blank" | "defaultIcon";
8
9
  backgroundColor?: string;
9
10
  color?: string;
@@ -0,0 +1,9 @@
1
+ import { TabProps, TypographyProps, Theme } from '@mui/material';
2
+ import { TabVariants } from './FoodsTabs';
3
+ import { StyledComponent } from '@emotion/styled';
4
+ import { MUIStyledCommonProps } from '@mui/system';
5
+ export declare const SpecialTab: StyledComponent<Omit<TabProps, "icon"> & {
6
+ variant: TabVariants;
7
+ isSelected: boolean;
8
+ textVariant: TypographyProps["variant"];
9
+ } & MUIStyledCommonProps<Theme>, {}, {}>;
@@ -0,0 +1,35 @@
1
+ import { BoxProps, TabProps, TabsProps, TypographyProps, TabsOwnProps, Theme } from '@mui/material';
2
+ import { ReactNode, DetailedHTMLProps, HTMLAttributes, DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES, RefObject } from 'react';
3
+ import { IconMappingProps } from '../../Icons';
4
+ import { StyledComponent } from '@emotion/styled';
5
+ import { CommonProps } from '@mui/material/OverridableComponent';
6
+ import { MUIStyledCommonProps } from '@mui/system';
7
+ export type TabVariants = "primary" | "secondary" | "buttons";
8
+ export type FoodsTabTemplateOption = {
9
+ name: ReactNode;
10
+ icon?: IconMappingProps;
11
+ disabled?: boolean;
12
+ TabProps?: TabProps;
13
+ };
14
+ export type FoodsTabsProps = {
15
+ tabs: FoodsTabTemplateOption[];
16
+ tabVariant: TabVariants;
17
+ textVariant?: TypographyProps["variant"];
18
+ selectedTabIndex?: number;
19
+ /**
20
+ * Force scrollable behavior even on desktop.
21
+ * If not provided, scrollable will be automatically enabled on mobile devices.
22
+ */
23
+ forceScrollable?: boolean;
24
+ BoxProps?: BoxProps;
25
+ TabsProps?: TabsProps;
26
+ TabProps?: TabProps;
27
+ };
28
+ export declare const FoodsTabs: (props: FoodsTabsProps) => import("react/jsx-runtime").JSX.Element;
29
+ export declare const SpecialTabs: StyledComponent< TabsOwnProps & CommonProps & Omit<Omit< DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
30
+ ref?: ((instance: HTMLDivElement | null) => void | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | RefObject<HTMLDivElement> | null | undefined;
31
+ }, "className" | "classes" | "style" | "children" | "aria-label" | "aria-labelledby" | "onChange" | "sx" | "slots" | "slotProps" | "variant" | "orientation" | "value" | "action" | "allowScrollButtonsMobile" | "centered" | "indicatorColor" | "ScrollButtonComponent" | "scrollButtons" | "selectionFollowsFocus" | "TabIndicatorProps" | "TabScrollButtonProps" | "textColor" | "visibleScrollbar"> & {
32
+ component?: React.ElementType;
33
+ } & {
34
+ tabVariant: TabVariants;
35
+ } & MUIStyledCommonProps<Theme>, {}, {}>;
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";
@@ -142,6 +143,8 @@ import { BredTheme, BredThemeOptions } from "./themes/themes/BredTheme.js";
142
143
  import { CollectiveTheme, CollectiveThemeOptions } from "./themes/themes/CollectiveTheme.js";
143
144
  import { FoodPilotTheme, FoodpilotThemeOptions } from "./themes/themes/FoodPilotTheme.js";
144
145
  import { WinePilotTheme, WinepilotThemeOptions } from "./themes/themes/WinePilotTheme.js";
146
+ import { useResponsivePadding } from "./themes/responsive/useResponsivePadding.js";
147
+ import { useResponsiveBreakpoints } from "./themes/responsive/useResponsiveBreakpoints.js";
145
148
  import { FoodsThemeOptionsProvider, FoodsThemeProvider } from "./themes/FoodsThemeProvider.js";
146
149
  export {
147
150
  AccordionProduct,
@@ -157,6 +160,7 @@ export {
157
160
  BlockListContainer,
158
161
  BottomSheetDialog,
159
162
  BoxStyled,
163
+ BreakdownTable,
160
164
  BredTheme,
161
165
  BredThemeOptions,
162
166
  Button,
@@ -322,5 +326,7 @@ export {
322
326
  themes,
323
327
  typographyConfig,
324
328
  useFormBaseContext,
329
+ useResponsiveBreakpoints,
330
+ useResponsivePadding,
325
331
  yellowTheme
326
332
  };
@@ -11,3 +11,4 @@ export declare const getThemeLogo: (theme: string) => React.FunctionComponent<Re
11
11
  export { FoodsThemeProvider, FoodsThemeOptionsProvider };
12
12
  export * from './common';
13
13
  export * from './themes';
14
+ export * from './responsive';
@@ -0,0 +1,2 @@
1
+ export { useResponsivePadding } from './useResponsivePadding';
2
+ export { useResponsiveBreakpoints } from './useResponsiveBreakpoints';
@@ -0,0 +1,6 @@
1
+ export declare const useResponsiveBreakpoints: () => {
2
+ isMobile: boolean;
3
+ isTablet: boolean;
4
+ isDesktop: boolean;
5
+ isUltrawide: boolean;
6
+ };
@@ -0,0 +1,18 @@
1
+ import { useTheme } from "@mui/material/styles";
2
+ import { useMediaQuery } from "@mui/material";
3
+ const useResponsiveBreakpoints = () => {
4
+ const theme = useTheme();
5
+ const isMobile = useMediaQuery(theme.breakpoints.down("md"));
6
+ const isTablet = useMediaQuery(theme.breakpoints.between("md", "lg"));
7
+ const isDesktop = useMediaQuery(theme.breakpoints.between("lg", "xl"));
8
+ const isUltrawide = useMediaQuery(theme.breakpoints.up("xl"));
9
+ return {
10
+ isMobile,
11
+ isTablet,
12
+ isDesktop,
13
+ isUltrawide
14
+ };
15
+ };
16
+ export {
17
+ useResponsiveBreakpoints
18
+ };
@@ -0,0 +1 @@
1
+ export declare const useResponsivePadding: () => "0.5rem" | "2rem" | "1.5rem" | "2.5rem";
@@ -0,0 +1,12 @@
1
+ import { useResponsiveBreakpoints } from "./useResponsiveBreakpoints.js";
2
+ const useResponsivePadding = () => {
3
+ const { isMobile, isTablet, isDesktop, isUltrawide } = useResponsiveBreakpoints();
4
+ if (isMobile) return "0.5rem";
5
+ if (isTablet) return "1.5rem";
6
+ if (isDesktop) return "2rem";
7
+ if (isUltrawide) return "2.5rem";
8
+ return "2rem";
9
+ };
10
+ export {
11
+ useResponsivePadding
12
+ };
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.10.0",
5
5
  "type": "module",
6
6
  "main": "./dist/main.js",
7
7
  "module": "./dist/main.js",