@apptimate/ui 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,7 +69,11 @@ export function DashboardLayout({
69
69
  const fullPath = basePath + (pathname === "/" && basePath ? "" : pathname);
70
70
 
71
71
  const currentMainMenu = menus.find(menu =>
72
- menu.groups.some(group => group.items.some(item => fullPath.startsWith(item.path)))
72
+ menu.groups.some(group => group.items.some(item =>
73
+ fullPath === item.path ||
74
+ fullPath.startsWith(item.path + "/") ||
75
+ item.path.startsWith(fullPath + "/")
76
+ ))
73
77
  ) || menus[0];
74
78
 
75
79
  const [activeMenuId, setActiveMenuId] = useState(currentMainMenu?.id || "");
@@ -162,7 +166,11 @@ export function DashboardLayout({
162
166
  <h3 className="px-2 text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">{group.label}</h3>
163
167
  <nav className="flex flex-col gap-1">
164
168
  {group.items.map((item) => {
165
- const isItemActive = fullPath === item.path || fullPath.startsWith(`${item.path}/`);
169
+ // Find the longest matching path in this group to avoid parent paths being active
170
+ const allGroupItems = activeMenu.groups.flatMap(g => g.items);
171
+ const matchingItems = allGroupItems.filter(i => fullPath === i.path || fullPath.startsWith(`${i.path}/`));
172
+ const longestMatch = matchingItems.sort((a, b) => b.path.length - a.path.length)[0];
173
+ const isItemActive = longestMatch?.path === item.path;
166
174
  const isInternal = basePath
167
175
  ? item.path.startsWith(basePath)
168
176
  : !externalPaths.some(ext => item.path.startsWith(ext));
@@ -244,8 +252,8 @@ export function DashboardLayout({
244
252
  </header>
245
253
 
246
254
  {/* Main Content Scrollable Area */}
247
- <main className="flex-1 p-4 md:p-6 lg:p-8 overflow-y-auto">
248
- <div className="bg-white rounded-2xl min-h-[calc(100vh-2rem)] md:min-h-[calc(100vh-4rem)] p-4 md:p-8">
255
+ <main className="flex-1 p-4 md:p-6 lg:p-8 overflow-hidden min-h-0">
256
+ <div className="bg-white rounded-2xl h-full p-4 md:p-8 flex flex-col overflow-y-auto">
249
257
  {children}
250
258
  </div>
251
259
  </main>
@@ -296,7 +304,10 @@ export function DashboardLayout({
296
304
  {group.label}
297
305
  </span>
298
306
  {group.items.map(item => {
299
- const isItemActive = fullPath === item.path || fullPath.startsWith(`${item.path}/`);
307
+ const allMenuItems = menu.groups.flatMap(g => g.items);
308
+ const matchingItems = allMenuItems.filter(i => fullPath === i.path || fullPath.startsWith(`${i.path}/`));
309
+ const longestMatch = matchingItems.sort((a, b) => b.path.length - a.path.length)[0];
310
+ const isItemActive = longestMatch?.path === item.path;
300
311
  const isInternal = basePath
301
312
  ? item.path.startsWith(basePath)
302
313
  : !externalPaths.some(ext => item.path.startsWith(ext));
@@ -0,0 +1,139 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import {
5
+ AreaChart,
6
+ Area,
7
+ XAxis,
8
+ YAxis,
9
+ CartesianGrid,
10
+ Tooltip,
11
+ Legend,
12
+ ResponsiveContainer,
13
+ } from "recharts";
14
+ import {
15
+ CHART_COLORS,
16
+ CHART_GRADIENTS,
17
+ TOOLTIP_STYLE,
18
+ TOOLTIP_ITEM_STYLE,
19
+ AXIS_TICK_STYLE,
20
+ GRID_STROKE,
21
+ ANIMATION_DURATION,
22
+ ANIMATION_EASING,
23
+ } from "./palette";
24
+
25
+ export interface RAreaChartSeries {
26
+ dataKey: string;
27
+ name?: string;
28
+ color?: string;
29
+ strokeWidth?: number;
30
+ type?: "monotone" | "linear" | "step" | "basis";
31
+ stackId?: string;
32
+ fillOpacity?: number;
33
+ }
34
+
35
+ export interface RAreaChartProps {
36
+ data: Record<string, any>[];
37
+ series: RAreaChartSeries[];
38
+ xAxisKey: string;
39
+ height?: number;
40
+ colors?: string[];
41
+ showGrid?: boolean;
42
+ showTooltip?: boolean;
43
+ showLegend?: boolean;
44
+ legendPosition?: "top" | "bottom";
45
+ xAxisFormatter?: (value: any) => string;
46
+ yAxisFormatter?: (value: any) => string;
47
+ tooltipFormatter?: (value: any, name: string) => [string, string];
48
+ className?: string;
49
+ }
50
+
51
+ export function RAreaChart({
52
+ data,
53
+ series,
54
+ xAxisKey,
55
+ height = 350,
56
+ colors = [...CHART_COLORS],
57
+ showGrid = true,
58
+ showTooltip = true,
59
+ showLegend = true,
60
+ legendPosition = "bottom",
61
+ xAxisFormatter,
62
+ yAxisFormatter,
63
+ tooltipFormatter,
64
+ className = "",
65
+ }: RAreaChartProps) {
66
+ const gradientId = React.useId();
67
+
68
+ return (
69
+ <div className={`w-full ${className}`} style={{ height }}>
70
+ <ResponsiveContainer width="100%" height="100%">
71
+ <AreaChart data={data} margin={{ top: 10, right: 10, left: 0, bottom: 10 }}>
72
+ <defs>
73
+ {series.map((s, idx) => {
74
+ const gradientPair = CHART_GRADIENTS[idx % CHART_GRADIENTS.length];
75
+ const id = `area-gradient-${gradientId}-${idx}`;
76
+ return (
77
+ <linearGradient key={id} id={id} x1="0" y1="0" x2="0" y2="1">
78
+ <stop offset="0%" stopColor={s.color || gradientPair[0]} stopOpacity={0.4} />
79
+ <stop offset="50%" stopColor={s.color || gradientPair[1]} stopOpacity={0.1} />
80
+ <stop offset="100%" stopColor={s.color || gradientPair[1]} stopOpacity={0} />
81
+ </linearGradient>
82
+ );
83
+ })}
84
+ </defs>
85
+
86
+ {showGrid && (
87
+ <CartesianGrid stroke={GRID_STROKE} strokeDasharray="0" />
88
+ )}
89
+ <XAxis
90
+ dataKey={xAxisKey}
91
+ tickFormatter={xAxisFormatter}
92
+ tick={AXIS_TICK_STYLE}
93
+ tickLine={false}
94
+ axisLine={false}
95
+ />
96
+ <YAxis
97
+ tickFormatter={yAxisFormatter}
98
+ tick={AXIS_TICK_STYLE}
99
+ tickLine={false}
100
+ axisLine={false}
101
+ />
102
+ {showTooltip && (
103
+ <Tooltip
104
+ formatter={tooltipFormatter}
105
+ contentStyle={TOOLTIP_STYLE}
106
+ itemStyle={TOOLTIP_ITEM_STYLE}
107
+ animationDuration={300}
108
+ />
109
+ )}
110
+ {showLegend && (
111
+ <Legend
112
+ verticalAlign={legendPosition}
113
+ wrapperStyle={{ fontSize: "12px", fontWeight: 700, paddingTop: "8px" }}
114
+ iconType="circle"
115
+ iconSize={8}
116
+ />
117
+ )}
118
+ {series.map((s, idx) => (
119
+ <Area
120
+ key={s.dataKey}
121
+ type={s.type || "monotone"}
122
+ dataKey={s.dataKey}
123
+ name={s.name || s.dataKey}
124
+ stroke={s.color || colors[idx % colors.length]}
125
+ strokeWidth={s.strokeWidth || 2.5}
126
+ fill={`url(#area-gradient-${gradientId}-${idx})`}
127
+ fillOpacity={s.fillOpacity ?? 1}
128
+ stackId={s.stackId}
129
+ activeDot={{ r: 5, fill: "#fff", stroke: s.color || colors[idx % colors.length], strokeWidth: 2.5 }}
130
+ isAnimationActive={true}
131
+ animationDuration={ANIMATION_DURATION}
132
+ animationEasing={ANIMATION_EASING}
133
+ />
134
+ ))}
135
+ </AreaChart>
136
+ </ResponsiveContainer>
137
+ </div>
138
+ );
139
+ }
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import {
5
+ BarChart,
6
+ Bar,
7
+ XAxis,
8
+ YAxis,
9
+ CartesianGrid,
10
+ Tooltip,
11
+ Legend,
12
+ ResponsiveContainer,
13
+ } from "recharts";
14
+ import {
15
+ CHART_COLORS,
16
+ CHART_GRADIENTS,
17
+ TOOLTIP_STYLE,
18
+ TOOLTIP_ITEM_STYLE,
19
+ AXIS_TICK_STYLE,
20
+ GRID_STROKE,
21
+ ANIMATION_DURATION,
22
+ ANIMATION_EASING,
23
+ } from "./palette";
24
+
25
+ export interface RBarChartSeries {
26
+ dataKey: string;
27
+ name?: string;
28
+ color?: string;
29
+ stackId?: string;
30
+ radius?: number;
31
+ }
32
+
33
+ export interface RBarChartProps {
34
+ data: Record<string, any>[];
35
+ series: RBarChartSeries[];
36
+ xAxisKey: string;
37
+ height?: number;
38
+ colors?: string[];
39
+ layout?: "horizontal" | "vertical";
40
+ barSize?: number;
41
+ showGrid?: boolean;
42
+ showTooltip?: boolean;
43
+ showLegend?: boolean;
44
+ legendPosition?: "top" | "bottom";
45
+ xAxisFormatter?: (value: any) => string;
46
+ yAxisFormatter?: (value: any) => string;
47
+ tooltipFormatter?: (value: any, name: string) => [string, string];
48
+ enableGradient?: boolean;
49
+ className?: string;
50
+ }
51
+
52
+ export function RBarChart({
53
+ data,
54
+ series,
55
+ xAxisKey,
56
+ height = 350,
57
+ colors = [...CHART_COLORS],
58
+ layout = "horizontal",
59
+ barSize,
60
+ showGrid = true,
61
+ showTooltip = true,
62
+ showLegend = true,
63
+ legendPosition = "bottom",
64
+ xAxisFormatter,
65
+ yAxisFormatter,
66
+ tooltipFormatter,
67
+ enableGradient = true,
68
+ className = "",
69
+ }: RBarChartProps) {
70
+ const gradientId = React.useId();
71
+
72
+ return (
73
+ <div className={`w-full ${className}`} style={{ height }}>
74
+ <ResponsiveContainer width="100%" height="100%">
75
+ <BarChart data={data} layout={layout} margin={{ top: 10, right: 10, left: 0, bottom: 10 }}>
76
+ {/* Gradient definitions */}
77
+ {enableGradient && (
78
+ <defs>
79
+ {series.map((s, idx) => {
80
+ const gradientPair = CHART_GRADIENTS[idx % CHART_GRADIENTS.length];
81
+ const id = `bar-gradient-${gradientId}-${idx}`;
82
+ return (
83
+ <linearGradient key={id} id={id} x1="0" y1="0" x2="0" y2="1">
84
+ <stop offset="0%" stopColor={s.color || gradientPair[0]} stopOpacity={1} />
85
+ <stop offset="100%" stopColor={s.color || gradientPair[1]} stopOpacity={0.7} />
86
+ </linearGradient>
87
+ );
88
+ })}
89
+ </defs>
90
+ )}
91
+
92
+ {showGrid && (
93
+ <CartesianGrid stroke={GRID_STROKE} strokeDasharray="0" />
94
+ )}
95
+ {layout === "horizontal" ? (
96
+ <>
97
+ <XAxis
98
+ dataKey={xAxisKey}
99
+ tickFormatter={xAxisFormatter}
100
+ tick={AXIS_TICK_STYLE}
101
+ tickLine={false}
102
+ axisLine={false}
103
+ />
104
+ <YAxis
105
+ tickFormatter={yAxisFormatter}
106
+ tick={AXIS_TICK_STYLE}
107
+ tickLine={false}
108
+ axisLine={false}
109
+ />
110
+ </>
111
+ ) : (
112
+ <>
113
+ <XAxis
114
+ type="number"
115
+ tickFormatter={xAxisFormatter}
116
+ tick={AXIS_TICK_STYLE}
117
+ tickLine={false}
118
+ axisLine={false}
119
+ />
120
+ <YAxis
121
+ type="category"
122
+ dataKey={xAxisKey}
123
+ tickFormatter={yAxisFormatter}
124
+ tick={AXIS_TICK_STYLE}
125
+ tickLine={false}
126
+ axisLine={false}
127
+ />
128
+ </>
129
+ )}
130
+ {showTooltip && (
131
+ <Tooltip
132
+ formatter={tooltipFormatter}
133
+ contentStyle={TOOLTIP_STYLE}
134
+ itemStyle={TOOLTIP_ITEM_STYLE}
135
+ cursor={{ fill: "rgba(99,102,241,0.06)" }}
136
+ animationDuration={300}
137
+ />
138
+ )}
139
+ {showLegend && (
140
+ <Legend
141
+ verticalAlign={legendPosition}
142
+ wrapperStyle={{ fontSize: "12px", fontWeight: 700, paddingTop: "8px" }}
143
+ iconType="circle"
144
+ iconSize={8}
145
+ />
146
+ )}
147
+ {series.map((s, idx) => {
148
+ const radius = s.radius !== undefined ? s.radius : 6;
149
+ const radiusArray =
150
+ layout === "vertical" ? [0, radius, radius, 0] : [radius, radius, 0, 0];
151
+
152
+ return (
153
+ <Bar
154
+ key={s.dataKey}
155
+ dataKey={s.dataKey}
156
+ name={s.name || s.dataKey}
157
+ fill={enableGradient ? `url(#bar-gradient-${gradientId}-${idx})` : (s.color || colors[idx % colors.length])}
158
+ stackId={s.stackId}
159
+ barSize={barSize}
160
+ radius={radiusArray as any}
161
+ isAnimationActive={true}
162
+ animationDuration={ANIMATION_DURATION}
163
+ animationEasing={ANIMATION_EASING}
164
+ />
165
+ );
166
+ })}
167
+ </BarChart>
168
+ </ResponsiveContainer>
169
+ </div>
170
+ );
171
+ }
@@ -0,0 +1,156 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import {
5
+ LineChart,
6
+ Line,
7
+ XAxis,
8
+ YAxis,
9
+ CartesianGrid,
10
+ Tooltip,
11
+ Legend,
12
+ ResponsiveContainer,
13
+ } from "recharts";
14
+ import {
15
+ CHART_COLORS,
16
+ CHART_GRADIENTS,
17
+ TOOLTIP_STYLE,
18
+ TOOLTIP_ITEM_STYLE,
19
+ AXIS_TICK_STYLE,
20
+ GRID_STROKE,
21
+ ANIMATION_DURATION,
22
+ ANIMATION_EASING,
23
+ } from "./palette";
24
+
25
+ export interface RLineChartSeries {
26
+ dataKey: string;
27
+ name?: string;
28
+ color?: string;
29
+ strokeWidth?: number;
30
+ type?: "monotone" | "linear" | "step" | "basis";
31
+ dashed?: boolean;
32
+ showDot?: boolean;
33
+ }
34
+
35
+ export interface RLineChartProps {
36
+ data: Record<string, any>[];
37
+ series: RLineChartSeries[];
38
+ xAxisKey: string;
39
+ height?: number;
40
+ colors?: string[];
41
+ showGrid?: boolean;
42
+ showTooltip?: boolean;
43
+ showLegend?: boolean;
44
+ legendPosition?: "top" | "bottom";
45
+ xAxisFormatter?: (value: any) => string;
46
+ yAxisFormatter?: (value: any) => string;
47
+ tooltipFormatter?: (value: any, name: string) => [string, string];
48
+ enableGradient?: boolean;
49
+ className?: string;
50
+ }
51
+
52
+ // Pulsing active dot component
53
+ function PulsingActiveDot(props: any) {
54
+ const { cx, cy, fill } = props;
55
+ return (
56
+ <g>
57
+ <circle cx={cx} cy={cy} r={8} fill={fill} opacity={0.15}>
58
+ <animate attributeName="r" values="8;14;8" dur="2s" repeatCount="indefinite" />
59
+ <animate attributeName="opacity" values="0.15;0.05;0.15" dur="2s" repeatCount="indefinite" />
60
+ </circle>
61
+ <circle cx={cx} cy={cy} r={5} fill="#fff" stroke={fill} strokeWidth={2.5} />
62
+ </g>
63
+ );
64
+ }
65
+
66
+ export function RLineChart({
67
+ data,
68
+ series,
69
+ xAxisKey,
70
+ height = 350,
71
+ colors = [...CHART_COLORS],
72
+ showGrid = true,
73
+ showTooltip = true,
74
+ showLegend = true,
75
+ legendPosition = "bottom",
76
+ xAxisFormatter,
77
+ yAxisFormatter,
78
+ tooltipFormatter,
79
+ enableGradient = true,
80
+ className = "",
81
+ }: RLineChartProps) {
82
+ const gradientId = React.useId();
83
+
84
+ return (
85
+ <div className={`w-full ${className}`} style={{ height }}>
86
+ <ResponsiveContainer width="100%" height="100%">
87
+ <LineChart data={data} margin={{ top: 10, right: 10, left: 0, bottom: 10 }}>
88
+ {/* Gradient definitions for area fill beneath lines */}
89
+ {enableGradient && (
90
+ <defs>
91
+ {series.map((s, idx) => {
92
+ const gradientPair = CHART_GRADIENTS[idx % CHART_GRADIENTS.length];
93
+ const id = `line-gradient-${gradientId}-${idx}`;
94
+ return (
95
+ <linearGradient key={id} id={id} x1="0" y1="0" x2="0" y2="1">
96
+ <stop offset="0%" stopColor={s.color || gradientPair[0]} stopOpacity={0.3} />
97
+ <stop offset="100%" stopColor={s.color || gradientPair[1]} stopOpacity={0} />
98
+ </linearGradient>
99
+ );
100
+ })}
101
+ </defs>
102
+ )}
103
+
104
+ {showGrid && (
105
+ <CartesianGrid stroke={GRID_STROKE} strokeDasharray="0" />
106
+ )}
107
+ <XAxis
108
+ dataKey={xAxisKey}
109
+ tickFormatter={xAxisFormatter}
110
+ tick={AXIS_TICK_STYLE}
111
+ tickLine={false}
112
+ axisLine={false}
113
+ />
114
+ <YAxis
115
+ tickFormatter={yAxisFormatter}
116
+ tick={AXIS_TICK_STYLE}
117
+ tickLine={false}
118
+ axisLine={false}
119
+ />
120
+ {showTooltip && (
121
+ <Tooltip
122
+ formatter={tooltipFormatter}
123
+ contentStyle={TOOLTIP_STYLE}
124
+ itemStyle={TOOLTIP_ITEM_STYLE}
125
+ animationDuration={300}
126
+ />
127
+ )}
128
+ {showLegend && (
129
+ <Legend
130
+ verticalAlign={legendPosition}
131
+ wrapperStyle={{ fontSize: "12px", fontWeight: 700, paddingTop: "8px" }}
132
+ iconType="circle"
133
+ iconSize={8}
134
+ />
135
+ )}
136
+ {series.map((s, idx) => (
137
+ <Line
138
+ key={s.dataKey}
139
+ type={s.type || "monotone"}
140
+ dataKey={s.dataKey}
141
+ name={s.name || s.dataKey}
142
+ stroke={s.color || colors[idx % colors.length]}
143
+ strokeWidth={s.strokeWidth || 2.5}
144
+ strokeDasharray={s.dashed ? "5 5" : undefined}
145
+ dot={s.showDot ? { r: 3, fill: "#fff", stroke: s.color || colors[idx % colors.length], strokeWidth: 2 } : false}
146
+ activeDot={<PulsingActiveDot fill={s.color || colors[idx % colors.length]} />}
147
+ isAnimationActive={true}
148
+ animationDuration={ANIMATION_DURATION}
149
+ animationEasing={ANIMATION_EASING}
150
+ />
151
+ ))}
152
+ </LineChart>
153
+ </ResponsiveContainer>
154
+ </div>
155
+ );
156
+ }