@xenide-io/the-old-ui-theme 0.9.7 → 0.9.8
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.
- package/dist/{chunk-2EO5VCXP.mjs → chunk-6SVJAU5K.mjs} +165 -1
- package/dist/index.mjs +1 -1
- package/dist/suite.mjs +1 -1
- package/dist/ui.d.mts +37 -1
- package/dist/ui.d.ts +37 -1
- package/dist/ui.js +170 -2
- package/dist/ui.mjs +11 -3
- package/package.json +1 -1
- package/src/components/ui/Charts.tsx +178 -0
- package/src/components/ui/charts.test.tsx +30 -0
- package/src/components/ui/index.ts +11 -0
|
@@ -3742,6 +3742,166 @@ var Chip = forwardRef11(function Chip2(_a, ref) {
|
|
|
3742
3742
|
});
|
|
3743
3743
|
Chip.displayName = "Chip";
|
|
3744
3744
|
|
|
3745
|
+
// src/lib/chart/use-ph-chart-tokens.ts
|
|
3746
|
+
import { useEffect as useEffect5, useState as useState10 } from "react";
|
|
3747
|
+
var DEFAULT_TOKENS = {
|
|
3748
|
+
series: ["#1d4aff", "#621da6", "#42827e", "#ce0e74", "#f14f58", "#529a0a", "#fe729e"],
|
|
3749
|
+
muted: "hsl(220 9% 90%)",
|
|
3750
|
+
surface: "#ffffff",
|
|
3751
|
+
textMuted: "hsl(220 9% 46%)",
|
|
3752
|
+
grid: "hsl(220 13% 91%)",
|
|
3753
|
+
borderStrong: "hsl(220 13% 80%)"
|
|
3754
|
+
};
|
|
3755
|
+
function readTokens() {
|
|
3756
|
+
if (typeof document === "undefined") return DEFAULT_TOKENS;
|
|
3757
|
+
const style = getComputedStyle(document.documentElement);
|
|
3758
|
+
const read = (key, fallback) => style.getPropertyValue(key).trim() || fallback;
|
|
3759
|
+
return {
|
|
3760
|
+
series: [
|
|
3761
|
+
read("--ph-data-1", DEFAULT_TOKENS.series[0]),
|
|
3762
|
+
read("--ph-data-2", DEFAULT_TOKENS.series[1]),
|
|
3763
|
+
read("--ph-data-3", DEFAULT_TOKENS.series[2]),
|
|
3764
|
+
read("--ph-data-4", DEFAULT_TOKENS.series[3]),
|
|
3765
|
+
read("--ph-data-5", DEFAULT_TOKENS.series[4]),
|
|
3766
|
+
read("--ph-data-6", DEFAULT_TOKENS.series[5]),
|
|
3767
|
+
read("--ph-data-7", DEFAULT_TOKENS.series[6])
|
|
3768
|
+
],
|
|
3769
|
+
muted: read("--ph-muted", DEFAULT_TOKENS.muted),
|
|
3770
|
+
surface: read("--ph-surface", DEFAULT_TOKENS.surface),
|
|
3771
|
+
textMuted: read("--ph-mutedtext", DEFAULT_TOKENS.textMuted),
|
|
3772
|
+
grid: read("--ph-border", DEFAULT_TOKENS.grid),
|
|
3773
|
+
borderStrong: read("--ph-border-strong", DEFAULT_TOKENS.borderStrong)
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
function useChartTokens() {
|
|
3777
|
+
const [tokens, setTokens] = useState10(DEFAULT_TOKENS);
|
|
3778
|
+
useEffect5(() => {
|
|
3779
|
+
setTokens(readTokens());
|
|
3780
|
+
}, []);
|
|
3781
|
+
return tokens;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
// src/components/ui/Charts.tsx
|
|
3785
|
+
import { jsx as jsx42, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
3786
|
+
function percent(value, total) {
|
|
3787
|
+
return total > 0 ? Math.round(value / total * 100) : 0;
|
|
3788
|
+
}
|
|
3789
|
+
function EmptyChart() {
|
|
3790
|
+
return /* @__PURE__ */ jsx42("p", { className: "text-sm text-ph-subtle", children: "No data for this period." });
|
|
3791
|
+
}
|
|
3792
|
+
function sliceColour(slice, index, series) {
|
|
3793
|
+
var _a;
|
|
3794
|
+
return (_a = slice.colour) != null ? _a : series[index % series.length];
|
|
3795
|
+
}
|
|
3796
|
+
function DonutChart({ slices, label, formatValue, className }) {
|
|
3797
|
+
const { series } = useChartTokens();
|
|
3798
|
+
const visible = slices.filter((slice) => slice.value > 0);
|
|
3799
|
+
const total = visible.reduce((sum, slice) => sum + slice.value, 0);
|
|
3800
|
+
if (total <= 0) return /* @__PURE__ */ jsx42(EmptyChart, {});
|
|
3801
|
+
const stops = visible.map((slice, index) => {
|
|
3802
|
+
const colour = sliceColour(slice, index, series);
|
|
3803
|
+
const before = visible.slice(0, index).reduce((sum, item) => sum + item.value, 0);
|
|
3804
|
+
const start = before / total * 360;
|
|
3805
|
+
const end = (before + slice.value) / total * 360;
|
|
3806
|
+
return `${colour} ${start}deg ${end}deg`;
|
|
3807
|
+
}).join(", ");
|
|
3808
|
+
return /* @__PURE__ */ jsxs36("div", { className: cn("flex flex-col items-center gap-4 sm:flex-row sm:items-center", className), children: [
|
|
3809
|
+
/* @__PURE__ */ jsx42(
|
|
3810
|
+
"div",
|
|
3811
|
+
{
|
|
3812
|
+
role: "img",
|
|
3813
|
+
"aria-label": label,
|
|
3814
|
+
className: "relative h-36 w-36 shrink-0 rounded-full",
|
|
3815
|
+
style: { background: `conic-gradient(${stops})` },
|
|
3816
|
+
children: /* @__PURE__ */ jsx42(
|
|
3817
|
+
"span",
|
|
3818
|
+
{
|
|
3819
|
+
className: "absolute inset-[22%] rounded-full bg-ph-surface ring-1 ring-ph-border",
|
|
3820
|
+
"aria-hidden": true
|
|
3821
|
+
}
|
|
3822
|
+
)
|
|
3823
|
+
}
|
|
3824
|
+
),
|
|
3825
|
+
/* @__PURE__ */ jsx42("ul", { className: "min-w-0 w-full flex-1 space-y-2", children: visible.map((slice, index) => {
|
|
3826
|
+
const colour = sliceColour(slice, index, series);
|
|
3827
|
+
return /* @__PURE__ */ jsxs36("li", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
3828
|
+
/* @__PURE__ */ jsxs36("span", { className: "flex min-w-0 items-center gap-2 text-ph-subtle", children: [
|
|
3829
|
+
/* @__PURE__ */ jsx42("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { background: colour } }),
|
|
3830
|
+
/* @__PURE__ */ jsx42("span", { className: "truncate", children: slice.label })
|
|
3831
|
+
] }),
|
|
3832
|
+
/* @__PURE__ */ jsx42("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%` })
|
|
3833
|
+
] }, slice.label);
|
|
3834
|
+
}) })
|
|
3835
|
+
] });
|
|
3836
|
+
}
|
|
3837
|
+
function BarChart({ items, formatValue, className }) {
|
|
3838
|
+
const { series } = useChartTokens();
|
|
3839
|
+
const visible = items.filter((item) => item.value > 0);
|
|
3840
|
+
const max = Math.max(...visible.map((item) => item.value), 1);
|
|
3841
|
+
if (!visible.length) return /* @__PURE__ */ jsx42(EmptyChart, {});
|
|
3842
|
+
return /* @__PURE__ */ jsx42("ul", { className: cn("space-y-3", className), children: visible.map((item, index) => {
|
|
3843
|
+
const colour = sliceColour(item, index, series);
|
|
3844
|
+
return /* @__PURE__ */ jsxs36("li", { children: [
|
|
3845
|
+
/* @__PURE__ */ jsxs36("div", { className: "mb-1 flex items-center justify-between gap-3 text-sm", children: [
|
|
3846
|
+
/* @__PURE__ */ jsx42("span", { className: "truncate text-ph-subtle", children: item.label }),
|
|
3847
|
+
/* @__PURE__ */ jsx42("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(item.value) : item.value.toLocaleString("en-AU") })
|
|
3848
|
+
] }),
|
|
3849
|
+
/* @__PURE__ */ jsx42("div", { className: "h-2.5 rounded-full bg-ph-muted ring-1 ring-ph-border", children: /* @__PURE__ */ jsx42(
|
|
3850
|
+
"div",
|
|
3851
|
+
{
|
|
3852
|
+
className: "h-full rounded-full transition-[width] duration-300 ease-out motion-reduce:transition-none",
|
|
3853
|
+
style: {
|
|
3854
|
+
width: `${Math.max(item.value / max * 100, 2)}%`,
|
|
3855
|
+
background: colour
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
) })
|
|
3859
|
+
] }, item.label);
|
|
3860
|
+
}) });
|
|
3861
|
+
}
|
|
3862
|
+
function StackedBarChart({
|
|
3863
|
+
slices,
|
|
3864
|
+
label,
|
|
3865
|
+
formatValue,
|
|
3866
|
+
className
|
|
3867
|
+
}) {
|
|
3868
|
+
const { series } = useChartTokens();
|
|
3869
|
+
const visible = slices.filter((slice) => slice.value > 0);
|
|
3870
|
+
const total = visible.reduce((sum, slice) => sum + slice.value, 0);
|
|
3871
|
+
if (total <= 0) return /* @__PURE__ */ jsx42(EmptyChart, {});
|
|
3872
|
+
return /* @__PURE__ */ jsxs36("div", { className: cn("space-y-3", className), children: [
|
|
3873
|
+
/* @__PURE__ */ jsx42(
|
|
3874
|
+
"div",
|
|
3875
|
+
{
|
|
3876
|
+
role: "img",
|
|
3877
|
+
"aria-label": label,
|
|
3878
|
+
className: "flex h-3 overflow-hidden rounded-full bg-ph-muted ring-1 ring-ph-border",
|
|
3879
|
+
children: visible.map((slice, index) => /* @__PURE__ */ jsx42(
|
|
3880
|
+
"span",
|
|
3881
|
+
{
|
|
3882
|
+
className: "h-full",
|
|
3883
|
+
style: {
|
|
3884
|
+
width: `${Math.max(slice.value / total * 100, 2)}%`,
|
|
3885
|
+
background: sliceColour(slice, index, series)
|
|
3886
|
+
}
|
|
3887
|
+
},
|
|
3888
|
+
slice.label
|
|
3889
|
+
))
|
|
3890
|
+
}
|
|
3891
|
+
),
|
|
3892
|
+
/* @__PURE__ */ jsx42("ul", { className: "space-y-2", children: visible.map((slice, index) => {
|
|
3893
|
+
const colour = sliceColour(slice, index, series);
|
|
3894
|
+
return /* @__PURE__ */ jsxs36("li", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
3895
|
+
/* @__PURE__ */ jsxs36("span", { className: "flex min-w-0 items-center gap-2 text-ph-subtle", children: [
|
|
3896
|
+
/* @__PURE__ */ jsx42("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { background: colour } }),
|
|
3897
|
+
/* @__PURE__ */ jsx42("span", { className: "truncate", children: slice.label })
|
|
3898
|
+
] }),
|
|
3899
|
+
/* @__PURE__ */ jsx42("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%` })
|
|
3900
|
+
] }, slice.label);
|
|
3901
|
+
}) })
|
|
3902
|
+
] });
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3745
3905
|
export {
|
|
3746
3906
|
ButtonChrome,
|
|
3747
3907
|
cn,
|
|
@@ -3908,5 +4068,9 @@ export {
|
|
|
3908
4068
|
Link,
|
|
3909
4069
|
Spinner,
|
|
3910
4070
|
Dot,
|
|
3911
|
-
Chip
|
|
4071
|
+
Chip,
|
|
4072
|
+
useChartTokens,
|
|
4073
|
+
DonutChart,
|
|
4074
|
+
BarChart,
|
|
4075
|
+
StackedBarChart
|
|
3912
4076
|
};
|
package/dist/index.mjs
CHANGED
package/dist/suite.mjs
CHANGED
package/dist/ui.d.mts
CHANGED
|
@@ -55,4 +55,40 @@ interface CollapsibleSectionProps {
|
|
|
55
55
|
}
|
|
56
56
|
declare function CollapsibleSection({ title, children, defaultOpen, id, }: CollapsibleSectionProps): react.JSX.Element;
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
interface ChartTokens {
|
|
59
|
+
series: string[];
|
|
60
|
+
muted: string;
|
|
61
|
+
surface: string;
|
|
62
|
+
textMuted: string;
|
|
63
|
+
grid: string;
|
|
64
|
+
borderStrong: string;
|
|
65
|
+
}
|
|
66
|
+
declare function useChartTokens(): ChartTokens;
|
|
67
|
+
|
|
68
|
+
interface ChartSlice {
|
|
69
|
+
label: string;
|
|
70
|
+
value: number;
|
|
71
|
+
colour?: string;
|
|
72
|
+
}
|
|
73
|
+
interface DonutChartProps {
|
|
74
|
+
slices: readonly ChartSlice[];
|
|
75
|
+
label: string;
|
|
76
|
+
formatValue?: (value: number) => string;
|
|
77
|
+
className?: string;
|
|
78
|
+
}
|
|
79
|
+
interface BarChartProps {
|
|
80
|
+
items: readonly ChartSlice[];
|
|
81
|
+
formatValue?: (value: number) => string;
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
interface StackedBarChartProps {
|
|
85
|
+
slices: readonly ChartSlice[];
|
|
86
|
+
label: string;
|
|
87
|
+
formatValue?: (value: number) => string;
|
|
88
|
+
className?: string;
|
|
89
|
+
}
|
|
90
|
+
declare function DonutChart({ slices, label, formatValue, className }: DonutChartProps): react.JSX.Element;
|
|
91
|
+
declare function BarChart({ items, formatValue, className }: BarChartProps): react.JSX.Element;
|
|
92
|
+
declare function StackedBarChart({ slices, label, formatValue, className, }: StackedBarChartProps): react.JSX.Element;
|
|
93
|
+
|
|
94
|
+
export { BarChart, type BarChartProps, type ChartSlice, type ChartTokens, CodeBlock, CollapsibleSection, type CollapsibleSectionProps, ComponentDocs, type ComponentDocsProps, type ComponentPropRow, DonutChart, type DonutChartProps, FilterBarProps, FilterChipsProps, FilterControls, type FilterControlsProps, ShowcaseWrapper, StackedBarChart, type StackedBarChartProps, useChartTokens };
|
package/dist/ui.d.ts
CHANGED
|
@@ -55,4 +55,40 @@ interface CollapsibleSectionProps {
|
|
|
55
55
|
}
|
|
56
56
|
declare function CollapsibleSection({ title, children, defaultOpen, id, }: CollapsibleSectionProps): react.JSX.Element;
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
interface ChartTokens {
|
|
59
|
+
series: string[];
|
|
60
|
+
muted: string;
|
|
61
|
+
surface: string;
|
|
62
|
+
textMuted: string;
|
|
63
|
+
grid: string;
|
|
64
|
+
borderStrong: string;
|
|
65
|
+
}
|
|
66
|
+
declare function useChartTokens(): ChartTokens;
|
|
67
|
+
|
|
68
|
+
interface ChartSlice {
|
|
69
|
+
label: string;
|
|
70
|
+
value: number;
|
|
71
|
+
colour?: string;
|
|
72
|
+
}
|
|
73
|
+
interface DonutChartProps {
|
|
74
|
+
slices: readonly ChartSlice[];
|
|
75
|
+
label: string;
|
|
76
|
+
formatValue?: (value: number) => string;
|
|
77
|
+
className?: string;
|
|
78
|
+
}
|
|
79
|
+
interface BarChartProps {
|
|
80
|
+
items: readonly ChartSlice[];
|
|
81
|
+
formatValue?: (value: number) => string;
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
interface StackedBarChartProps {
|
|
85
|
+
slices: readonly ChartSlice[];
|
|
86
|
+
label: string;
|
|
87
|
+
formatValue?: (value: number) => string;
|
|
88
|
+
className?: string;
|
|
89
|
+
}
|
|
90
|
+
declare function DonutChart({ slices, label, formatValue, className }: DonutChartProps): react.JSX.Element;
|
|
91
|
+
declare function BarChart({ items, formatValue, className }: BarChartProps): react.JSX.Element;
|
|
92
|
+
declare function StackedBarChart({ slices, label, formatValue, className, }: StackedBarChartProps): react.JSX.Element;
|
|
93
|
+
|
|
94
|
+
export { BarChart, type BarChartProps, type ChartSlice, type ChartTokens, CodeBlock, CollapsibleSection, type CollapsibleSectionProps, ComponentDocs, type ComponentDocsProps, type ComponentPropRow, DonutChart, type DonutChartProps, FilterBarProps, FilterChipsProps, FilterControls, type FilterControlsProps, ShowcaseWrapper, StackedBarChart, type StackedBarChartProps, useChartTokens };
|
package/dist/ui.js
CHANGED
|
@@ -68,6 +68,7 @@ __export(ui_exports, {
|
|
|
68
68
|
Avatar: () => Avatar,
|
|
69
69
|
AvatarGroup: () => AvatarGroup,
|
|
70
70
|
Badge: () => Badge,
|
|
71
|
+
BarChart: () => BarChart,
|
|
71
72
|
Button: () => Button,
|
|
72
73
|
ButtonChrome: () => ButtonChrome,
|
|
73
74
|
Calendar: () => Calendar,
|
|
@@ -80,6 +81,7 @@ __export(ui_exports, {
|
|
|
80
81
|
CommandPalette: () => CommandPalette,
|
|
81
82
|
ComponentDocs: () => ComponentDocs,
|
|
82
83
|
Display: () => Display,
|
|
84
|
+
DonutChart: () => DonutChart,
|
|
83
85
|
Dot: () => Dot,
|
|
84
86
|
DropdownButton: () => DropdownButton,
|
|
85
87
|
DropdownItem: () => DropdownItem,
|
|
@@ -127,6 +129,7 @@ __export(ui_exports, {
|
|
|
127
129
|
Small: () => Small,
|
|
128
130
|
SortMenu: () => SortMenu,
|
|
129
131
|
Spinner: () => Spinner,
|
|
132
|
+
StackedBarChart: () => StackedBarChart,
|
|
130
133
|
Stat: () => Stat,
|
|
131
134
|
Table: () => Table,
|
|
132
135
|
Textarea: () => Textarea,
|
|
@@ -134,7 +137,8 @@ __export(ui_exports, {
|
|
|
134
137
|
ThemeSwitcher: () => ThemeSwitcher,
|
|
135
138
|
Toggle: () => Toggle,
|
|
136
139
|
Tooltip: () => Tooltip,
|
|
137
|
-
TooltipProvider: () => TooltipProvider
|
|
140
|
+
TooltipProvider: () => TooltipProvider,
|
|
141
|
+
useChartTokens: () => useChartTokens
|
|
138
142
|
});
|
|
139
143
|
module.exports = __toCommonJS(ui_exports);
|
|
140
144
|
|
|
@@ -3847,6 +3851,166 @@ var Chip = (0, import_react25.forwardRef)(function Chip2(_a, ref) {
|
|
|
3847
3851
|
);
|
|
3848
3852
|
});
|
|
3849
3853
|
Chip.displayName = "Chip";
|
|
3854
|
+
|
|
3855
|
+
// src/lib/chart/use-ph-chart-tokens.ts
|
|
3856
|
+
var import_react26 = require("react");
|
|
3857
|
+
var DEFAULT_TOKENS = {
|
|
3858
|
+
series: ["#1d4aff", "#621da6", "#42827e", "#ce0e74", "#f14f58", "#529a0a", "#fe729e"],
|
|
3859
|
+
muted: "hsl(220 9% 90%)",
|
|
3860
|
+
surface: "#ffffff",
|
|
3861
|
+
textMuted: "hsl(220 9% 46%)",
|
|
3862
|
+
grid: "hsl(220 13% 91%)",
|
|
3863
|
+
borderStrong: "hsl(220 13% 80%)"
|
|
3864
|
+
};
|
|
3865
|
+
function readTokens() {
|
|
3866
|
+
if (typeof document === "undefined") return DEFAULT_TOKENS;
|
|
3867
|
+
const style = getComputedStyle(document.documentElement);
|
|
3868
|
+
const read = (key, fallback) => style.getPropertyValue(key).trim() || fallback;
|
|
3869
|
+
return {
|
|
3870
|
+
series: [
|
|
3871
|
+
read("--ph-data-1", DEFAULT_TOKENS.series[0]),
|
|
3872
|
+
read("--ph-data-2", DEFAULT_TOKENS.series[1]),
|
|
3873
|
+
read("--ph-data-3", DEFAULT_TOKENS.series[2]),
|
|
3874
|
+
read("--ph-data-4", DEFAULT_TOKENS.series[3]),
|
|
3875
|
+
read("--ph-data-5", DEFAULT_TOKENS.series[4]),
|
|
3876
|
+
read("--ph-data-6", DEFAULT_TOKENS.series[5]),
|
|
3877
|
+
read("--ph-data-7", DEFAULT_TOKENS.series[6])
|
|
3878
|
+
],
|
|
3879
|
+
muted: read("--ph-muted", DEFAULT_TOKENS.muted),
|
|
3880
|
+
surface: read("--ph-surface", DEFAULT_TOKENS.surface),
|
|
3881
|
+
textMuted: read("--ph-mutedtext", DEFAULT_TOKENS.textMuted),
|
|
3882
|
+
grid: read("--ph-border", DEFAULT_TOKENS.grid),
|
|
3883
|
+
borderStrong: read("--ph-border-strong", DEFAULT_TOKENS.borderStrong)
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
function useChartTokens() {
|
|
3887
|
+
const [tokens, setTokens] = (0, import_react26.useState)(DEFAULT_TOKENS);
|
|
3888
|
+
(0, import_react26.useEffect)(() => {
|
|
3889
|
+
setTokens(readTokens());
|
|
3890
|
+
}, []);
|
|
3891
|
+
return tokens;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
// src/components/ui/Charts.tsx
|
|
3895
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
3896
|
+
function percent(value, total) {
|
|
3897
|
+
return total > 0 ? Math.round(value / total * 100) : 0;
|
|
3898
|
+
}
|
|
3899
|
+
function EmptyChart() {
|
|
3900
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm text-ph-subtle", children: "No data for this period." });
|
|
3901
|
+
}
|
|
3902
|
+
function sliceColour(slice, index, series) {
|
|
3903
|
+
var _a;
|
|
3904
|
+
return (_a = slice.colour) != null ? _a : series[index % series.length];
|
|
3905
|
+
}
|
|
3906
|
+
function DonutChart({ slices, label, formatValue, className }) {
|
|
3907
|
+
const { series } = useChartTokens();
|
|
3908
|
+
const visible = slices.filter((slice) => slice.value > 0);
|
|
3909
|
+
const total = visible.reduce((sum, slice) => sum + slice.value, 0);
|
|
3910
|
+
if (total <= 0) return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(EmptyChart, {});
|
|
3911
|
+
const stops = visible.map((slice, index) => {
|
|
3912
|
+
const colour = sliceColour(slice, index, series);
|
|
3913
|
+
const before = visible.slice(0, index).reduce((sum, item) => sum + item.value, 0);
|
|
3914
|
+
const start = before / total * 360;
|
|
3915
|
+
const end = (before + slice.value) / total * 360;
|
|
3916
|
+
return `${colour} ${start}deg ${end}deg`;
|
|
3917
|
+
}).join(", ");
|
|
3918
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: cn("flex flex-col items-center gap-4 sm:flex-row sm:items-center", className), children: [
|
|
3919
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3920
|
+
"div",
|
|
3921
|
+
{
|
|
3922
|
+
role: "img",
|
|
3923
|
+
"aria-label": label,
|
|
3924
|
+
className: "relative h-36 w-36 shrink-0 rounded-full",
|
|
3925
|
+
style: { background: `conic-gradient(${stops})` },
|
|
3926
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3927
|
+
"span",
|
|
3928
|
+
{
|
|
3929
|
+
className: "absolute inset-[22%] rounded-full bg-ph-surface ring-1 ring-ph-border",
|
|
3930
|
+
"aria-hidden": true
|
|
3931
|
+
}
|
|
3932
|
+
)
|
|
3933
|
+
}
|
|
3934
|
+
),
|
|
3935
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "min-w-0 w-full flex-1 space-y-2", children: visible.map((slice, index) => {
|
|
3936
|
+
const colour = sliceColour(slice, index, series);
|
|
3937
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
3938
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "flex min-w-0 items-center gap-2 text-ph-subtle", children: [
|
|
3939
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { background: colour } }),
|
|
3940
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "truncate", children: slice.label })
|
|
3941
|
+
] }),
|
|
3942
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%` })
|
|
3943
|
+
] }, slice.label);
|
|
3944
|
+
}) })
|
|
3945
|
+
] });
|
|
3946
|
+
}
|
|
3947
|
+
function BarChart({ items, formatValue, className }) {
|
|
3948
|
+
const { series } = useChartTokens();
|
|
3949
|
+
const visible = items.filter((item) => item.value > 0);
|
|
3950
|
+
const max = Math.max(...visible.map((item) => item.value), 1);
|
|
3951
|
+
if (!visible.length) return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(EmptyChart, {});
|
|
3952
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: cn("space-y-3", className), children: visible.map((item, index) => {
|
|
3953
|
+
const colour = sliceColour(item, index, series);
|
|
3954
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { children: [
|
|
3955
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "mb-1 flex items-center justify-between gap-3 text-sm", children: [
|
|
3956
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "truncate text-ph-subtle", children: item.label }),
|
|
3957
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(item.value) : item.value.toLocaleString("en-AU") })
|
|
3958
|
+
] }),
|
|
3959
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-2.5 rounded-full bg-ph-muted ring-1 ring-ph-border", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3960
|
+
"div",
|
|
3961
|
+
{
|
|
3962
|
+
className: "h-full rounded-full transition-[width] duration-300 ease-out motion-reduce:transition-none",
|
|
3963
|
+
style: {
|
|
3964
|
+
width: `${Math.max(item.value / max * 100, 2)}%`,
|
|
3965
|
+
background: colour
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
) })
|
|
3969
|
+
] }, item.label);
|
|
3970
|
+
}) });
|
|
3971
|
+
}
|
|
3972
|
+
function StackedBarChart({
|
|
3973
|
+
slices,
|
|
3974
|
+
label,
|
|
3975
|
+
formatValue,
|
|
3976
|
+
className
|
|
3977
|
+
}) {
|
|
3978
|
+
const { series } = useChartTokens();
|
|
3979
|
+
const visible = slices.filter((slice) => slice.value > 0);
|
|
3980
|
+
const total = visible.reduce((sum, slice) => sum + slice.value, 0);
|
|
3981
|
+
if (total <= 0) return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(EmptyChart, {});
|
|
3982
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: cn("space-y-3", className), children: [
|
|
3983
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3984
|
+
"div",
|
|
3985
|
+
{
|
|
3986
|
+
role: "img",
|
|
3987
|
+
"aria-label": label,
|
|
3988
|
+
className: "flex h-3 overflow-hidden rounded-full bg-ph-muted ring-1 ring-ph-border",
|
|
3989
|
+
children: visible.map((slice, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3990
|
+
"span",
|
|
3991
|
+
{
|
|
3992
|
+
className: "h-full",
|
|
3993
|
+
style: {
|
|
3994
|
+
width: `${Math.max(slice.value / total * 100, 2)}%`,
|
|
3995
|
+
background: sliceColour(slice, index, series)
|
|
3996
|
+
}
|
|
3997
|
+
},
|
|
3998
|
+
slice.label
|
|
3999
|
+
))
|
|
4000
|
+
}
|
|
4001
|
+
),
|
|
4002
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "space-y-2", children: visible.map((slice, index) => {
|
|
4003
|
+
const colour = sliceColour(slice, index, series);
|
|
4004
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
4005
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "flex min-w-0 items-center gap-2 text-ph-subtle", children: [
|
|
4006
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { background: colour } }),
|
|
4007
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "truncate", children: slice.label })
|
|
4008
|
+
] }),
|
|
4009
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%` })
|
|
4010
|
+
] }, slice.label);
|
|
4011
|
+
}) })
|
|
4012
|
+
] });
|
|
4013
|
+
}
|
|
3850
4014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3851
4015
|
0 && (module.exports = {
|
|
3852
4016
|
Accordion,
|
|
@@ -3857,6 +4021,7 @@ Chip.displayName = "Chip";
|
|
|
3857
4021
|
Avatar,
|
|
3858
4022
|
AvatarGroup,
|
|
3859
4023
|
Badge,
|
|
4024
|
+
BarChart,
|
|
3860
4025
|
Button,
|
|
3861
4026
|
ButtonChrome,
|
|
3862
4027
|
Calendar,
|
|
@@ -3869,6 +4034,7 @@ Chip.displayName = "Chip";
|
|
|
3869
4034
|
CommandPalette,
|
|
3870
4035
|
ComponentDocs,
|
|
3871
4036
|
Display,
|
|
4037
|
+
DonutChart,
|
|
3872
4038
|
Dot,
|
|
3873
4039
|
DropdownButton,
|
|
3874
4040
|
DropdownItem,
|
|
@@ -3916,6 +4082,7 @@ Chip.displayName = "Chip";
|
|
|
3916
4082
|
Small,
|
|
3917
4083
|
SortMenu,
|
|
3918
4084
|
Spinner,
|
|
4085
|
+
StackedBarChart,
|
|
3919
4086
|
Stat,
|
|
3920
4087
|
Table,
|
|
3921
4088
|
Textarea,
|
|
@@ -3923,5 +4090,6 @@ Chip.displayName = "Chip";
|
|
|
3923
4090
|
ThemeSwitcher,
|
|
3924
4091
|
Toggle,
|
|
3925
4092
|
Tooltip,
|
|
3926
|
-
TooltipProvider
|
|
4093
|
+
TooltipProvider,
|
|
4094
|
+
useChartTokens
|
|
3927
4095
|
});
|
package/dist/ui.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Avatar,
|
|
9
9
|
AvatarGroup,
|
|
10
10
|
Badge,
|
|
11
|
+
BarChart,
|
|
11
12
|
Button,
|
|
12
13
|
ButtonChrome,
|
|
13
14
|
Calendar,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
CommandPalette,
|
|
21
22
|
ComponentDocs,
|
|
22
23
|
Display,
|
|
24
|
+
DonutChart,
|
|
23
25
|
Dot,
|
|
24
26
|
DropdownButton,
|
|
25
27
|
DropdownItem,
|
|
@@ -67,6 +69,7 @@ import {
|
|
|
67
69
|
Small,
|
|
68
70
|
SortMenu,
|
|
69
71
|
Spinner,
|
|
72
|
+
StackedBarChart,
|
|
70
73
|
Stat,
|
|
71
74
|
Table,
|
|
72
75
|
Textarea,
|
|
@@ -74,8 +77,9 @@ import {
|
|
|
74
77
|
ThemeSwitcher,
|
|
75
78
|
Toggle,
|
|
76
79
|
Tooltip,
|
|
77
|
-
TooltipProvider
|
|
78
|
-
|
|
80
|
+
TooltipProvider,
|
|
81
|
+
useChartTokens
|
|
82
|
+
} from "./chunk-6SVJAU5K.mjs";
|
|
79
83
|
import "./chunk-FWCSY2DS.mjs";
|
|
80
84
|
export {
|
|
81
85
|
Accordion,
|
|
@@ -86,6 +90,7 @@ export {
|
|
|
86
90
|
Avatar,
|
|
87
91
|
AvatarGroup,
|
|
88
92
|
Badge,
|
|
93
|
+
BarChart,
|
|
89
94
|
Button,
|
|
90
95
|
ButtonChrome,
|
|
91
96
|
Calendar,
|
|
@@ -98,6 +103,7 @@ export {
|
|
|
98
103
|
CommandPalette,
|
|
99
104
|
ComponentDocs,
|
|
100
105
|
Display,
|
|
106
|
+
DonutChart,
|
|
101
107
|
Dot,
|
|
102
108
|
DropdownButton,
|
|
103
109
|
DropdownItem,
|
|
@@ -145,6 +151,7 @@ export {
|
|
|
145
151
|
Small,
|
|
146
152
|
SortMenu,
|
|
147
153
|
Spinner,
|
|
154
|
+
StackedBarChart,
|
|
148
155
|
Stat,
|
|
149
156
|
Table,
|
|
150
157
|
Textarea,
|
|
@@ -152,5 +159,6 @@ export {
|
|
|
152
159
|
ThemeSwitcher,
|
|
153
160
|
Toggle,
|
|
154
161
|
Tooltip,
|
|
155
|
-
TooltipProvider
|
|
162
|
+
TooltipProvider,
|
|
163
|
+
useChartTokens
|
|
156
164
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenide-io/the-old-ui-theme",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Props-driven React components and theme tokens inspired by classic interface design — optimized for Next.js apps and internal tools.",
|
|
5
5
|
"author": "Xenide",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useChartTokens } from "@/lib/chart/use-ph-chart-tokens";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
|
|
6
|
+
export { useChartTokens, type ChartTokens } from "@/lib/chart/use-ph-chart-tokens";
|
|
7
|
+
|
|
8
|
+
export interface ChartSlice {
|
|
9
|
+
label: string;
|
|
10
|
+
value: number;
|
|
11
|
+
colour?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DonutChartProps {
|
|
15
|
+
slices: readonly ChartSlice[];
|
|
16
|
+
label: string;
|
|
17
|
+
formatValue?: (value: number) => string;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BarChartProps {
|
|
22
|
+
items: readonly ChartSlice[];
|
|
23
|
+
formatValue?: (value: number) => string;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StackedBarChartProps {
|
|
28
|
+
slices: readonly ChartSlice[];
|
|
29
|
+
label: string;
|
|
30
|
+
formatValue?: (value: number) => string;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function percent(value: number, total: number): number {
|
|
35
|
+
return total > 0 ? Math.round((value / total) * 100) : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function EmptyChart() {
|
|
39
|
+
return <p className="text-sm text-ph-subtle">No data for this period.</p>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sliceColour(slice: ChartSlice, index: number, series: string[]): string {
|
|
43
|
+
return slice.colour ?? series[index % series.length];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function DonutChart({ slices, label, formatValue, className }: DonutChartProps) {
|
|
47
|
+
const { series } = useChartTokens();
|
|
48
|
+
const visible = slices.filter((slice) => slice.value > 0);
|
|
49
|
+
const total = visible.reduce((sum, slice) => sum + slice.value, 0);
|
|
50
|
+
if (total <= 0) return <EmptyChart />;
|
|
51
|
+
|
|
52
|
+
const stops = visible
|
|
53
|
+
.map((slice, index) => {
|
|
54
|
+
const colour = sliceColour(slice, index, series);
|
|
55
|
+
const before = visible
|
|
56
|
+
.slice(0, index)
|
|
57
|
+
.reduce((sum, item) => sum + item.value, 0);
|
|
58
|
+
const start = (before / total) * 360;
|
|
59
|
+
const end = ((before + slice.value) / total) * 360;
|
|
60
|
+
return `${colour} ${start}deg ${end}deg`;
|
|
61
|
+
})
|
|
62
|
+
.join(", ");
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className={cn("flex flex-col items-center gap-4 sm:flex-row sm:items-center", className)}>
|
|
66
|
+
<div
|
|
67
|
+
role="img"
|
|
68
|
+
aria-label={label}
|
|
69
|
+
className="relative h-36 w-36 shrink-0 rounded-full"
|
|
70
|
+
style={{ background: `conic-gradient(${stops})` }}
|
|
71
|
+
>
|
|
72
|
+
<span
|
|
73
|
+
className="absolute inset-[22%] rounded-full bg-ph-surface ring-1 ring-ph-border"
|
|
74
|
+
aria-hidden
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
<ul className="min-w-0 w-full flex-1 space-y-2">
|
|
78
|
+
{visible.map((slice, index) => {
|
|
79
|
+
const colour = sliceColour(slice, index, series);
|
|
80
|
+
return (
|
|
81
|
+
<li key={slice.label} className="flex items-center justify-between gap-3 text-sm">
|
|
82
|
+
<span className="flex min-w-0 items-center gap-2 text-ph-subtle">
|
|
83
|
+
<span className="h-2.5 w-2.5 shrink-0 rounded-full" style={{ background: colour }} />
|
|
84
|
+
<span className="truncate">{slice.label}</span>
|
|
85
|
+
</span>
|
|
86
|
+
<span className="shrink-0 tabular-nums text-ph-ink">
|
|
87
|
+
{formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%`}
|
|
88
|
+
</span>
|
|
89
|
+
</li>
|
|
90
|
+
);
|
|
91
|
+
})}
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function BarChart({ items, formatValue, className }: BarChartProps) {
|
|
98
|
+
const { series } = useChartTokens();
|
|
99
|
+
const visible = items.filter((item) => item.value > 0);
|
|
100
|
+
const max = Math.max(...visible.map((item) => item.value), 1);
|
|
101
|
+
if (!visible.length) return <EmptyChart />;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<ul className={cn("space-y-3", className)}>
|
|
105
|
+
{visible.map((item, index) => {
|
|
106
|
+
const colour = sliceColour(item, index, series);
|
|
107
|
+
return (
|
|
108
|
+
<li key={item.label}>
|
|
109
|
+
<div className="mb-1 flex items-center justify-between gap-3 text-sm">
|
|
110
|
+
<span className="truncate text-ph-subtle">{item.label}</span>
|
|
111
|
+
<span className="shrink-0 tabular-nums text-ph-ink">
|
|
112
|
+
{formatValue ? formatValue(item.value) : item.value.toLocaleString("en-AU")}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="h-2.5 rounded-full bg-ph-muted ring-1 ring-ph-border">
|
|
116
|
+
<div
|
|
117
|
+
className="h-full rounded-full transition-[width] duration-300 ease-out motion-reduce:transition-none"
|
|
118
|
+
style={{
|
|
119
|
+
width: `${Math.max((item.value / max) * 100, 2)}%`,
|
|
120
|
+
background: colour,
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
</li>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</ul>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function StackedBarChart({
|
|
132
|
+
slices,
|
|
133
|
+
label,
|
|
134
|
+
formatValue,
|
|
135
|
+
className,
|
|
136
|
+
}: StackedBarChartProps) {
|
|
137
|
+
const { series } = useChartTokens();
|
|
138
|
+
const visible = slices.filter((slice) => slice.value > 0);
|
|
139
|
+
const total = visible.reduce((sum, slice) => sum + slice.value, 0);
|
|
140
|
+
if (total <= 0) return <EmptyChart />;
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div className={cn("space-y-3", className)}>
|
|
144
|
+
<div
|
|
145
|
+
role="img"
|
|
146
|
+
aria-label={label}
|
|
147
|
+
className="flex h-3 overflow-hidden rounded-full bg-ph-muted ring-1 ring-ph-border"
|
|
148
|
+
>
|
|
149
|
+
{visible.map((slice, index) => (
|
|
150
|
+
<span
|
|
151
|
+
key={slice.label}
|
|
152
|
+
className="h-full"
|
|
153
|
+
style={{
|
|
154
|
+
width: `${Math.max((slice.value / total) * 100, 2)}%`,
|
|
155
|
+
background: sliceColour(slice, index, series),
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
<ul className="space-y-2">
|
|
161
|
+
{visible.map((slice, index) => {
|
|
162
|
+
const colour = sliceColour(slice, index, series);
|
|
163
|
+
return (
|
|
164
|
+
<li key={slice.label} className="flex items-center justify-between gap-3 text-sm">
|
|
165
|
+
<span className="flex min-w-0 items-center gap-2 text-ph-subtle">
|
|
166
|
+
<span className="h-2.5 w-2.5 shrink-0 rounded-full" style={{ background: colour }} />
|
|
167
|
+
<span className="truncate">{slice.label}</span>
|
|
168
|
+
</span>
|
|
169
|
+
<span className="shrink-0 tabular-nums text-ph-ink">
|
|
170
|
+
{formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%`}
|
|
171
|
+
</span>
|
|
172
|
+
</li>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
</ul>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { BarChart, DonutChart, StackedBarChart } from "./Charts";
|
|
5
|
+
|
|
6
|
+
const slices = [
|
|
7
|
+
{ label: "Tides", value: 40 },
|
|
8
|
+
{ label: "TurtleTime", value: 60 },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
describe("Charts", () => {
|
|
12
|
+
it("renders a labelled donut and a bar for each slice", () => {
|
|
13
|
+
render(<DonutChart label="Time by source" slices={slices} />);
|
|
14
|
+
expect(screen.getByRole("img", { name: "Time by source" })).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByText("Tides")).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByText("40%")).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("renders bars with formatted values", () => {
|
|
20
|
+
render(<BarChart items={slices} formatValue={(value) => `${value}h`} />);
|
|
21
|
+
expect(screen.getByText("60h")).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("renders a stacked mix and an empty state", () => {
|
|
25
|
+
const { rerender } = render(<StackedBarChart label="Work mix" slices={slices} />);
|
|
26
|
+
expect(screen.getByRole("img", { name: "Work mix" })).toBeInTheDocument();
|
|
27
|
+
rerender(<StackedBarChart label="Work mix" slices={[{ label: "None", value: 0 }]} />);
|
|
28
|
+
expect(screen.getByText("No data for this period.")).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -264,3 +264,14 @@ export {
|
|
|
264
264
|
Chip as Chip,
|
|
265
265
|
type ChipProps as ChipProps,
|
|
266
266
|
} from "@/components/ui/Chip";
|
|
267
|
+
export {
|
|
268
|
+
BarChart as BarChart,
|
|
269
|
+
DonutChart as DonutChart,
|
|
270
|
+
StackedBarChart as StackedBarChart,
|
|
271
|
+
useChartTokens as useChartTokens,
|
|
272
|
+
type BarChartProps as BarChartProps,
|
|
273
|
+
type ChartSlice as ChartSlice,
|
|
274
|
+
type ChartTokens as ChartTokens,
|
|
275
|
+
type DonutChartProps as DonutChartProps,
|
|
276
|
+
type StackedBarChartProps as StackedBarChartProps,
|
|
277
|
+
} from "@/components/ui/Charts";
|