@analytix/dashboard 0.2.3 → 0.2.4
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/AnalyticsDashboard.js +4 -21
- package/dist/AnalyticsDashboardSkeleton.js +1 -1
- package/dist/dashboard.css +127 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/useDashboardTheme.d.ts +8 -0
- package/dist/useDashboardTheme.js +51 -0
- package/package.json +2 -2
- package/src/dashboard.css +127 -38
|
@@ -3,9 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { DEFAULT_DASHBOARD_WIDGETS, percentChange } from "@analytix/core";
|
|
5
5
|
import { Area, CartesianGrid, ComposedChart, Line, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts";
|
|
6
|
-
import { Download, Eye, Users, Clock, MousePointerClick, Repeat, FileText, UserPlus, UserCheck, Moon,
|
|
6
|
+
import { Download, Eye, Users, Clock, MousePointerClick, Repeat, FileText, UserPlus, UserCheck, Sun, Moon, Monitor, SlidersHorizontal, } from "lucide-react";
|
|
7
7
|
import { AnalyticsDashboardSkeleton } from "./AnalyticsDashboardSkeleton";
|
|
8
8
|
import { useDashboardWidgets } from "./useDashboardWidgets";
|
|
9
|
+
import { DASHBOARD_THEME_LABELS, useDashboardTheme } from "./useDashboardTheme";
|
|
9
10
|
import { WidgetCustomizePanel } from "./WidgetCustomizePanel";
|
|
10
11
|
function formatBucketLabel(value, granularity) {
|
|
11
12
|
const date = new Date(value);
|
|
@@ -32,21 +33,6 @@ function Delta({ current, previous }) {
|
|
|
32
33
|
function BreakdownPanel({ title, emptyMessage, rows, }) {
|
|
33
34
|
return (_jsxs("div", { className: "chartPanel", children: [_jsx("div", { className: "panelTitle", children: title }), rows.length === 0 ? (_jsx("p", { className: "emptyState", children: emptyMessage })) : (_jsx("ul", { className: "list", children: rows.map((row) => (_jsxs("li", { children: [_jsx("span", { className: "listLabel", children: row.label }), _jsx("span", { className: "listValue", children: row.value })] }, row.key))) }))] }));
|
|
34
35
|
}
|
|
35
|
-
function useResolvedTheme(mode) {
|
|
36
|
-
const [resolved, setResolved] = useState("light");
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (mode !== "system") {
|
|
39
|
-
setResolved(mode);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
43
|
-
const apply = () => setResolved(media.matches ? "dark" : "light");
|
|
44
|
-
apply();
|
|
45
|
-
media.addEventListener("change", apply);
|
|
46
|
-
return () => media.removeEventListener("change", apply);
|
|
47
|
-
}, [mode]);
|
|
48
|
-
return resolved;
|
|
49
|
-
}
|
|
50
36
|
export function AnalyticsDashboard({ siteId, summaryEndpoint, exportEndpoint, loadingFallback, defaultTheme = "system", defaultWidgets = DEFAULT_DASHBOARD_WIDGETS, settingsEndpoint, onWidgetsSaved, onWidgetsSaveError, }) {
|
|
51
37
|
const summaryUrl = summaryEndpoint ?? `/api/v1/sites/${siteId}/summary`;
|
|
52
38
|
const exportUrl = exportEndpoint ?? `/api/v1/sites/${siteId}/export`;
|
|
@@ -63,8 +49,7 @@ export function AnalyticsDashboard({ siteId, summaryEndpoint, exportEndpoint, lo
|
|
|
63
49
|
const [initialLoading, setInitialLoading] = useState(true);
|
|
64
50
|
const [refreshing, setRefreshing] = useState(false);
|
|
65
51
|
const [error, setError] = useState("");
|
|
66
|
-
const
|
|
67
|
-
const resolvedTheme = useResolvedTheme(themeMode);
|
|
52
|
+
const { themeMode, cycleTheme, resolvedTheme } = useDashboardTheme(siteId, defaultTheme);
|
|
68
53
|
const hasLoadedRef = useRef(false);
|
|
69
54
|
const [showWidgetCustomize, setShowWidgetCustomize] = useState(false);
|
|
70
55
|
const [savingDefaultWidgets, setSavingDefaultWidgets] = useState(false);
|
|
@@ -196,9 +181,7 @@ export function AnalyticsDashboard({ siteId, summaryEndpoint, exportEndpoint, lo
|
|
|
196
181
|
setSavingDefaultWidgets(false);
|
|
197
182
|
}
|
|
198
183
|
}
|
|
199
|
-
return (_jsxs("div", { className: `analytix-dash ${themeClass}${refreshing ? " analytix-refreshing" : ""}`, children: [_jsxs("div", { className: "toolbar", children: [_jsxs("span", { className: "realtime", children: [summary.realtime_visitors, " visitors in the last 15 minutes"] }), _jsxs("div", { className: "toolbarActions", children: [_jsx("button", { type: "button", className: "btn btnIcon", onClick: () => setShowWidgetCustomize((open) => !open), "aria-label": "Customize widgets", children: _jsx(SlidersHorizontal, { size: 16 }) }), _jsx("button", { type: "button", className: "btn btnIcon", onClick:
|
|
200
|
-
? "light"
|
|
201
|
-
: "dark"), "aria-label": "Toggle theme", children: resolvedTheme === "dark" ? _jsx(Sun, { size: 16 }) : _jsx(Moon, { size: 16 }) }), _jsxs("a", { className: "btn", href: `${exportUrl}?${filterParams.toString()}`, children: [_jsx(Download, { size: 14 }), "Export CSV"] })] })] }), showWidgetCustomize ? (_jsx(WidgetCustomizePanel, { widgets: widgets, onToggle: toggleWidget, onReset: resetToDefault, onSaveDefault: settingsEndpoint ? saveDefaultWidgets : undefined, savingDefault: savingDefaultWidgets })) : null, _jsxs("div", { className: "filters", children: [_jsxs("div", { className: "filterGroup filterGroupWide", children: [_jsx("span", { className: "filterLabel", children: "Range" }), _jsxs("div", { className: "chips", children: [["24h", "7d", "30d", "90d"].map((key) => (_jsx("button", { type: "button", className: !useCustomRange && range === key ? "chipActive" : "chip", onClick: () => {
|
|
184
|
+
return (_jsxs("div", { className: `analytix-dash ${themeClass}${refreshing ? " analytix-refreshing" : ""}`, children: [_jsxs("div", { className: "toolbar", children: [_jsxs("span", { className: "realtime", children: [summary.realtime_visitors, " visitors in the last 15 minutes"] }), _jsxs("div", { className: "toolbarActions", children: [_jsx("button", { type: "button", className: "btn btnIcon", onClick: () => setShowWidgetCustomize((open) => !open), "aria-label": "Customize widgets", children: _jsx(SlidersHorizontal, { size: 16 }) }), _jsx("button", { type: "button", className: "btn btnIcon", onClick: cycleTheme, "aria-label": `Theme: ${DASHBOARD_THEME_LABELS[themeMode]}. Click to change.`, title: `Theme: ${DASHBOARD_THEME_LABELS[themeMode]}`, children: themeMode === "system" ? (_jsx(Monitor, { size: 16 })) : resolvedTheme === "dark" ? (_jsx(Sun, { size: 16 })) : (_jsx(Moon, { size: 16 })) }), _jsxs("a", { className: "btn", href: `${exportUrl}?${filterParams.toString()}`, children: [_jsx(Download, { size: 14 }), "Export CSV"] })] })] }), showWidgetCustomize ? (_jsx(WidgetCustomizePanel, { widgets: widgets, onToggle: toggleWidget, onReset: resetToDefault, onSaveDefault: settingsEndpoint ? saveDefaultWidgets : undefined, savingDefault: savingDefaultWidgets })) : null, _jsxs("div", { className: "filters", children: [_jsxs("div", { className: "filterGroup filterGroupWide", children: [_jsx("span", { className: "filterLabel", children: "Range" }), _jsxs("div", { className: "chips", children: [["24h", "7d", "30d", "90d"].map((key) => (_jsx("button", { type: "button", className: !useCustomRange && range === key ? "chipActive" : "chip", onClick: () => {
|
|
202
185
|
setUseCustomRange(false);
|
|
203
186
|
setRange(key);
|
|
204
187
|
}, children: key }, key))), _jsx("button", { type: "button", className: useCustomRange ? "chipActive" : "chip", onClick: () => setUseCustomRange(true), children: "Custom" })] }), useCustomRange && (_jsxs("div", { className: "dateRangeRow", children: [_jsxs("label", { children: [_jsx("span", { className: "filterLabel", children: "From" }), _jsx("input", { type: "date", value: customFrom, max: customTo, onChange: (e) => setCustomFrom(e.target.value) })] }), _jsxs("label", { children: [_jsx("span", { className: "filterLabel", children: "To" }), _jsx("input", { type: "date", value: customTo, min: customFrom, max: toDateInputValue(new Date()), onChange: (e) => setCustomTo(e.target.value) })] })] }))] }), _jsxs("div", { className: "filterGroup", children: [_jsx("span", { className: "filterLabel", children: "Granularity" }), _jsx("div", { className: "chips", children: ["hour", "day"].map((key) => (_jsx("button", { type: "button", className: granularity === key ? "chipActive" : "chip", onClick: () => setGranularity(key), children: key }, key))) })] }), _jsxs("div", { className: "filterGroup", children: [_jsx("span", { className: "filterLabel", children: "Scope" }), _jsx("div", { className: "chips", children: ["all", "page", "blog"].map((key) => (_jsx("button", { type: "button", className: trafficScope === key ? "chipActive" : "chip", onClick: () => setTrafficScope(key), children: key }, key))) })] }), trafficScope === "page" && (_jsxs("div", { className: "filterGroup filterGroupWide textField", children: [_jsx("span", { className: "filterLabel", children: "Path" }), _jsx("input", { value: path, onChange: (e) => setPath(e.target.value), placeholder: "/pricing" }), path.trim() === "/blog" && (_jsxs("label", { className: "checkboxRow", children: [_jsx("input", { type: "checkbox", checked: includeBlogArticles, onChange: (e) => setIncludeBlogArticles(e.target.checked) }), "Include blog articles"] }))] })), trafficScope === "blog" && (_jsxs("div", { className: "filterGroup filterGroupWide textField", children: [_jsx("span", { className: "filterLabel", children: "Content ID (optional)" }), _jsx("input", { value: contentId, onChange: (e) => setContentId(e.target.value), placeholder: "Filter to a single article" })] }))] }), isVisible("metrics") ? (_jsxs("div", { className: "metrics", children: [_jsxs("div", { className: "metricCard", children: [_jsx(Eye, { size: 18 }), _jsx("span", { className: "metricValue", children: formatNumber(summary.total_page_views) }), _jsx("span", { className: "metricLabel", children: "Page views" }), _jsx(Delta, { current: summary.total_page_views, previous: prev?.total_page_views })] }), _jsxs("div", { className: "metricCard", children: [_jsx(Users, { size: 18 }), _jsx("span", { className: "metricValue", children: formatNumber(summary.unique_visitors) }), _jsx("span", { className: "metricLabel", children: "Unique visitors" }), _jsx(Delta, { current: summary.unique_visitors, previous: prev?.unique_visitors })] }), _jsxs("div", { className: "metricCard", children: [_jsx(Repeat, { size: 18 }), _jsx("span", { className: "metricValue", children: formatNumber(summary.total_sessions) }), _jsx("span", { className: "metricLabel", children: "Sessions" }), _jsx(Delta, { current: summary.total_sessions, previous: prev?.total_sessions })] }), _jsxs("div", { className: "metricCard", children: [_jsx(FileText, { size: 18 }), _jsx("span", { className: "metricValue", children: summary.pages_per_session.toFixed(2) }), _jsx("span", { className: "metricLabel", children: "Pages / session" })] }), _jsxs("div", { className: "metricCard", children: [_jsx(UserPlus, { size: 18 }), _jsx("span", { className: "metricValue", children: formatNumber(summary.new_visitors) }), _jsx("span", { className: "metricLabel", children: "New visitors" })] }), _jsxs("div", { className: "metricCard", children: [_jsx(UserCheck, { size: 18 }), _jsx("span", { className: "metricValue", children: formatNumber(summary.returning_visitors) }), _jsx("span", { className: "metricLabel", children: "Returning visitors" })] }), _jsxs("div", { className: "metricCard", children: [_jsx(MousePointerClick, { size: 18 }), _jsxs("span", { className: "metricValue", children: [summary.bounce_rate, "%"] }), _jsx("span", { className: "metricLabel", children: "Bounce rate" }), _jsx(Delta, { current: summary.bounce_rate, previous: prev?.bounce_rate })] }), _jsxs("div", { className: "metricCard", children: [_jsx(Clock, { size: 18 }), _jsxs("span", { className: "metricValue", children: [summary.avg_engagement_seconds, "s"] }), _jsx("span", { className: "metricLabel", children: "Avg engagement" }), _jsx(Delta, { current: summary.avg_engagement_seconds, previous: prev?.avg_engagement_seconds })] })] })) : null, isVisible("chart") ? (_jsxs("div", { className: "chartPanel", children: [_jsx("div", { className: "panelTitle", children: "Traffic over time" }), _jsx("div", { className: "chartContainer", children: chartData.length === 0 ? (_jsx("p", { className: "emptyState", children: "No traffic recorded for this period." })) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: chartData, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), _jsx(XAxis, { dataKey: "label", tick: { fontSize: 12, fill: "var(--ax-muted)" } }), _jsx(YAxis, { tick: { fontSize: 12, fill: "var(--ax-muted)" } }), _jsx(Tooltip, { contentStyle: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
export function AnalyticsDashboardSkeleton() {
|
|
3
|
-
return (_jsxs("div", { className: "analytix-dash analytix-skeleton", "aria-busy": "true", "aria-label": "Loading analytics", children: [_jsxs("div", { className: "toolbar", children: [_jsx("div", { className: "sk skRealtime" }), _jsx("div", { className: "sk skBtn" })] }), _jsx("div", { className: "filters", children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "filterGroup", children: [_jsx("div", { className: "sk skLabel" }), _jsxs("div", { className: "skRow", children: [_jsx("div", { className: "sk skChip" }), _jsx("div", { className: "sk skChip" }), _jsx("div", { className: "sk skChip" })] })] }, i))) }), _jsx("div", { className: "metrics", children: Array.from({ length: 7 }).map((_, i) => (_jsxs("div", { className: "metricCard", children: [_jsx("div", { className: "sk skIcon" }), _jsx("div", { className: "sk skMetricValue" }), _jsx("div", { className: "sk skMetricLabel" }), _jsx("div", { className: "sk skDelta" })] }, i))) }), _jsxs("div", { className: "chartPanel", children: [_jsx("div", { className: "sk skPanelTitle" }), _jsx("div", { className: "sk skChart" })] }), _jsx("div", { className: "splitPanels", children: Array.from({ length: 2 }).map((_, i) => (_jsxs("div", { className: "chartPanel", children: [_jsx("div", { className: "sk skPanelTitle" }), _jsx("div", { className: "skList", children: Array.from({ length: 5 }).map((__, j) => (_jsx("div", { className: "sk skListRow" }, j))) })] }, i))) })] }));
|
|
3
|
+
return (_jsxs("div", { className: "analytix-dash analytix-theme-light analytix-skeleton", "aria-busy": "true", "aria-label": "Loading analytics", children: [_jsxs("div", { className: "toolbar", children: [_jsx("div", { className: "sk skRealtime" }), _jsx("div", { className: "sk skBtn" })] }), _jsx("div", { className: "filters", children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "filterGroup", children: [_jsx("div", { className: "sk skLabel" }), _jsxs("div", { className: "skRow", children: [_jsx("div", { className: "sk skChip" }), _jsx("div", { className: "sk skChip" }), _jsx("div", { className: "sk skChip" })] })] }, i))) }), _jsx("div", { className: "metrics", children: Array.from({ length: 7 }).map((_, i) => (_jsxs("div", { className: "metricCard", children: [_jsx("div", { className: "sk skIcon" }), _jsx("div", { className: "sk skMetricValue" }), _jsx("div", { className: "sk skMetricLabel" }), _jsx("div", { className: "sk skDelta" })] }, i))) }), _jsxs("div", { className: "chartPanel", children: [_jsx("div", { className: "sk skPanelTitle" }), _jsx("div", { className: "sk skChart" })] }), _jsx("div", { className: "splitPanels", children: Array.from({ length: 2 }).map((_, i) => (_jsxs("div", { className: "chartPanel", children: [_jsx("div", { className: "sk skPanelTitle" }), _jsx("div", { className: "skList", children: Array.from({ length: 5 }).map((__, j) => (_jsx("div", { className: "sk skListRow" }, j))) })] }, i))) })] }));
|
|
4
4
|
}
|
package/dist/dashboard.css
CHANGED
|
@@ -1,42 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embeddable dashboard styles.
|
|
3
|
+
* Host apps may override tokens via --analytix-dash-* on a parent element.
|
|
4
|
+
*/
|
|
1
5
|
.analytix-dash {
|
|
2
|
-
--ax-primary: #
|
|
3
|
-
--ax-muted: #
|
|
4
|
-
--ax-accent: #
|
|
5
|
-
--ax-surface: rgba(255, 255, 255, 0.
|
|
6
|
-
--ax-surface-solid: #ffffff;
|
|
7
|
-
--ax-border: rgba(
|
|
8
|
-
--ax-success: #
|
|
9
|
-
--ax-success-bg:
|
|
10
|
-
--ax-danger: #
|
|
11
|
-
--ax-up: #
|
|
12
|
-
--ax-down: #
|
|
13
|
-
--ax-chart-grid: rgba(
|
|
6
|
+
--ax-primary: var(--analytix-dash-ink, #111111);
|
|
7
|
+
--ax-muted: var(--analytix-dash-muted, #787774);
|
|
8
|
+
--ax-accent: var(--analytix-dash-accent, #111111);
|
|
9
|
+
--ax-surface: var(--analytix-dash-surface, rgba(255, 255, 255, 0.82));
|
|
10
|
+
--ax-surface-solid: var(--analytix-dash-surface-solid, #ffffff);
|
|
11
|
+
--ax-border: var(--analytix-dash-border, rgba(17, 17, 17, 0.08));
|
|
12
|
+
--ax-success: var(--analytix-dash-success, #346538);
|
|
13
|
+
--ax-success-bg: var(--analytix-dash-success-bg, #edf3ec);
|
|
14
|
+
--ax-danger: var(--analytix-dash-danger, #9f2f2d);
|
|
15
|
+
--ax-up: #346538;
|
|
16
|
+
--ax-down: #9f2f2d;
|
|
17
|
+
--ax-chart-grid: rgba(17, 17, 17, 0.08);
|
|
14
18
|
display: flex;
|
|
15
19
|
flex-direction: column;
|
|
16
20
|
gap: 24px;
|
|
17
21
|
width: 100%;
|
|
22
|
+
min-width: 0;
|
|
18
23
|
color: var(--ax-primary);
|
|
19
|
-
font-family:
|
|
24
|
+
font-family: var(
|
|
25
|
+
--analytix-dash-font,
|
|
26
|
+
system-ui,
|
|
27
|
+
-apple-system,
|
|
28
|
+
BlinkMacSystemFont,
|
|
29
|
+
"Segoe UI",
|
|
30
|
+
sans-serif
|
|
31
|
+
);
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
.analytix-dash.analytix-theme-dark {
|
|
23
|
-
--ax-primary: #
|
|
24
|
-
--ax-muted: #
|
|
25
|
-
--ax-accent: #
|
|
26
|
-
--ax-surface: rgba(
|
|
27
|
-
--ax-surface-solid: #
|
|
28
|
-
--ax-border: rgba(
|
|
29
|
-
--ax-success: #
|
|
30
|
-
--ax-success-bg: rgba(52,
|
|
31
|
-
--ax-
|
|
32
|
-
--ax-
|
|
33
|
-
--ax-
|
|
35
|
+
--ax-primary: var(--analytix-dash-ink, #f7f6f3);
|
|
36
|
+
--ax-muted: var(--analytix-dash-muted, #9b9a97);
|
|
37
|
+
--ax-accent: var(--analytix-dash-accent, #f7f6f3);
|
|
38
|
+
--ax-surface: var(--analytix-dash-surface, rgba(25, 25, 24, 0.88));
|
|
39
|
+
--ax-surface-solid: var(--analytix-dash-surface-solid, #191918);
|
|
40
|
+
--ax-border: var(--analytix-dash-border, rgba(255, 255, 255, 0.1));
|
|
41
|
+
--ax-success: #6ee7b7;
|
|
42
|
+
--ax-success-bg: rgba(52, 101, 56, 0.22);
|
|
43
|
+
--ax-danger: #fca5a5;
|
|
44
|
+
--ax-up: #6ee7b7;
|
|
45
|
+
--ax-down: #fca5a5;
|
|
46
|
+
--ax-chart-grid: rgba(255, 255, 255, 0.08);
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
.analytix-dash.analytix-refreshing {
|
|
37
50
|
opacity: 0.72;
|
|
38
51
|
pointer-events: none;
|
|
39
|
-
transition: opacity 0.
|
|
52
|
+
transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
.analytix-dash .filters {
|
|
@@ -45,7 +58,7 @@
|
|
|
45
58
|
gap: 14px;
|
|
46
59
|
padding: 18px;
|
|
47
60
|
border: 1px solid var(--ax-border);
|
|
48
|
-
border-radius:
|
|
61
|
+
border-radius: 12px;
|
|
49
62
|
background: var(--ax-surface);
|
|
50
63
|
}
|
|
51
64
|
|
|
@@ -83,17 +96,33 @@
|
|
|
83
96
|
font-weight: 600;
|
|
84
97
|
cursor: pointer;
|
|
85
98
|
color: var(--ax-muted);
|
|
99
|
+
transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
100
|
+
background 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
101
|
+
border-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
102
|
+
color 160ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@media (hover: hover) and (pointer: fine) {
|
|
106
|
+
.analytix-dash .chip:hover {
|
|
107
|
+
color: var(--ax-primary);
|
|
108
|
+
border-color: var(--ax-accent);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.analytix-dash .chip:active,
|
|
113
|
+
.analytix-dash .chipActive:active {
|
|
114
|
+
transform: scale(0.97);
|
|
86
115
|
}
|
|
87
116
|
|
|
88
117
|
.analytix-dash .chipActive {
|
|
89
|
-
background: rgba(
|
|
90
|
-
border-color: rgba(
|
|
118
|
+
background: rgba(17, 17, 17, 0.06);
|
|
119
|
+
border-color: rgba(17, 17, 17, 0.14);
|
|
91
120
|
color: var(--ax-accent);
|
|
92
121
|
}
|
|
93
122
|
|
|
94
123
|
.analytix-dash.analytix-theme-dark .chipActive {
|
|
95
|
-
background: rgba(
|
|
96
|
-
border-color: rgba(
|
|
124
|
+
background: rgba(255, 255, 255, 0.08);
|
|
125
|
+
border-color: rgba(255, 255, 255, 0.16);
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
.analytix-dash .dateRangeRow {
|
|
@@ -115,9 +144,17 @@
|
|
|
115
144
|
font: inherit;
|
|
116
145
|
padding: 10px 12px;
|
|
117
146
|
border: 1px solid var(--ax-border);
|
|
118
|
-
border-radius:
|
|
147
|
+
border-radius: 8px;
|
|
119
148
|
background: var(--ax-surface-solid);
|
|
120
149
|
color: var(--ax-primary);
|
|
150
|
+
transition: border-color 160ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.analytix-dash .textField input:focus,
|
|
154
|
+
.analytix-dash .dateRangeRow input:focus,
|
|
155
|
+
.analytix-dash select:focus {
|
|
156
|
+
outline: none;
|
|
157
|
+
border-color: var(--ax-accent);
|
|
121
158
|
}
|
|
122
159
|
|
|
123
160
|
.analytix-dash .checkboxRow {
|
|
@@ -139,7 +176,7 @@
|
|
|
139
176
|
flex-direction: column;
|
|
140
177
|
gap: 8px;
|
|
141
178
|
padding: 18px;
|
|
142
|
-
border-radius:
|
|
179
|
+
border-radius: 12px;
|
|
143
180
|
border: 1px solid var(--ax-border);
|
|
144
181
|
background: var(--ax-surface);
|
|
145
182
|
color: var(--ax-accent);
|
|
@@ -151,6 +188,7 @@
|
|
|
151
188
|
font-weight: 600;
|
|
152
189
|
color: var(--ax-primary);
|
|
153
190
|
line-height: 1.2;
|
|
191
|
+
font-variant-numeric: tabular-nums;
|
|
154
192
|
}
|
|
155
193
|
|
|
156
194
|
.analytix-dash .metricLabel {
|
|
@@ -173,7 +211,7 @@
|
|
|
173
211
|
|
|
174
212
|
.analytix-dash .chartPanel {
|
|
175
213
|
padding: 18px;
|
|
176
|
-
border-radius:
|
|
214
|
+
border-radius: 12px;
|
|
177
215
|
border: 1px solid var(--ax-border);
|
|
178
216
|
background: var(--ax-surface);
|
|
179
217
|
min-width: 0;
|
|
@@ -183,11 +221,15 @@
|
|
|
183
221
|
width: 100%;
|
|
184
222
|
height: 280px;
|
|
185
223
|
min-height: 280px;
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
186
227
|
}
|
|
187
228
|
|
|
188
229
|
.analytix-dash .panelTitle {
|
|
189
|
-
font-size: 0.
|
|
230
|
+
font-size: 0.75rem;
|
|
190
231
|
font-weight: 700;
|
|
232
|
+
letter-spacing: 0.06em;
|
|
191
233
|
text-transform: uppercase;
|
|
192
234
|
color: var(--ax-muted);
|
|
193
235
|
margin-bottom: 14px;
|
|
@@ -250,6 +292,7 @@
|
|
|
250
292
|
display: flex;
|
|
251
293
|
gap: 8px;
|
|
252
294
|
align-items: center;
|
|
295
|
+
flex-wrap: wrap;
|
|
253
296
|
}
|
|
254
297
|
|
|
255
298
|
.analytix-dash .btn {
|
|
@@ -265,6 +308,18 @@
|
|
|
265
308
|
font-weight: 600;
|
|
266
309
|
color: var(--ax-primary);
|
|
267
310
|
text-decoration: none;
|
|
311
|
+
transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
312
|
+
background 160ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@media (hover: hover) and (pointer: fine) {
|
|
316
|
+
.analytix-dash .btn:hover {
|
|
317
|
+
background: var(--ax-surface);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.analytix-dash .btn:active {
|
|
322
|
+
transform: scale(0.97);
|
|
268
323
|
}
|
|
269
324
|
|
|
270
325
|
.analytix-dash .btnIcon {
|
|
@@ -272,27 +327,49 @@
|
|
|
272
327
|
}
|
|
273
328
|
|
|
274
329
|
.analytix-dash .realtime {
|
|
330
|
+
display: inline-flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 8px;
|
|
275
333
|
padding: 10px 14px;
|
|
276
|
-
border-radius:
|
|
334
|
+
border-radius: 9999px;
|
|
277
335
|
background: var(--ax-success-bg);
|
|
278
336
|
color: var(--ax-success);
|
|
279
337
|
font-size: 0.875rem;
|
|
280
338
|
font-weight: 600;
|
|
281
339
|
}
|
|
282
340
|
|
|
341
|
+
.analytix-dash .realtime::before {
|
|
342
|
+
content: "";
|
|
343
|
+
width: 7px;
|
|
344
|
+
height: 7px;
|
|
345
|
+
border-radius: 50%;
|
|
346
|
+
background: currentColor;
|
|
347
|
+
opacity: 0.85;
|
|
348
|
+
}
|
|
349
|
+
|
|
283
350
|
/* Skeleton */
|
|
284
351
|
.analytix-dash.analytix-skeleton .sk {
|
|
285
352
|
border-radius: 8px;
|
|
286
353
|
background: linear-gradient(
|
|
287
354
|
90deg,
|
|
288
|
-
rgba(
|
|
289
|
-
rgba(
|
|
290
|
-
rgba(
|
|
355
|
+
rgba(120, 119, 116, 0.1) 0%,
|
|
356
|
+
rgba(120, 119, 116, 0.18) 50%,
|
|
357
|
+
rgba(120, 119, 116, 0.1) 100%
|
|
291
358
|
);
|
|
292
359
|
background-size: 200% 100%;
|
|
293
360
|
animation: analytix-shimmer 1.4s ease-in-out infinite;
|
|
294
361
|
}
|
|
295
362
|
|
|
363
|
+
.analytix-dash.analytix-theme-dark.analytix-skeleton .sk {
|
|
364
|
+
background: linear-gradient(
|
|
365
|
+
90deg,
|
|
366
|
+
rgba(255, 255, 255, 0.06) 0%,
|
|
367
|
+
rgba(255, 255, 255, 0.12) 50%,
|
|
368
|
+
rgba(255, 255, 255, 0.06) 100%
|
|
369
|
+
);
|
|
370
|
+
background-size: 200% 100%;
|
|
371
|
+
}
|
|
372
|
+
|
|
296
373
|
.analytix-dash.analytix-skeleton .skRow {
|
|
297
374
|
display: flex;
|
|
298
375
|
gap: 6px;
|
|
@@ -302,6 +379,7 @@
|
|
|
302
379
|
.analytix-dash.analytix-skeleton .skRealtime {
|
|
303
380
|
width: 260px;
|
|
304
381
|
height: 40px;
|
|
382
|
+
border-radius: 9999px;
|
|
305
383
|
}
|
|
306
384
|
|
|
307
385
|
.analytix-dash.analytix-skeleton .skBtn {
|
|
@@ -372,6 +450,17 @@
|
|
|
372
450
|
}
|
|
373
451
|
}
|
|
374
452
|
|
|
453
|
+
@media (prefers-reduced-motion: reduce) {
|
|
454
|
+
.analytix-dash.analytix-skeleton .sk {
|
|
455
|
+
animation: none;
|
|
456
|
+
background: rgba(120, 119, 116, 0.12);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.analytix-dash.analytix-theme-dark.analytix-skeleton .sk {
|
|
460
|
+
background: rgba(255, 255, 255, 0.08);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
375
464
|
@media (max-width: 900px) {
|
|
376
465
|
.analytix-dash .filters,
|
|
377
466
|
.analytix-dash .splitPanels {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { AnalyticsDashboard, type AnalyticsDashboardProps } from "./AnalyticsDas
|
|
|
2
2
|
export { AnalyticsDashboardSkeleton } from "./AnalyticsDashboardSkeleton";
|
|
3
3
|
export { WidgetCustomizePanel } from "./WidgetCustomizePanel";
|
|
4
4
|
export { useDashboardWidgets } from "./useDashboardWidgets";
|
|
5
|
+
export { useDashboardTheme, DASHBOARD_THEME_LABELS, type DashboardThemeMode } from "./useDashboardTheme";
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { AnalyticsDashboard } from "./AnalyticsDashboard";
|
|
|
2
2
|
export { AnalyticsDashboardSkeleton } from "./AnalyticsDashboardSkeleton";
|
|
3
3
|
export { WidgetCustomizePanel } from "./WidgetCustomizePanel";
|
|
4
4
|
export { useDashboardWidgets } from "./useDashboardWidgets";
|
|
5
|
+
export { useDashboardTheme, DASHBOARD_THEME_LABELS } from "./useDashboardTheme";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type DashboardThemeMode = "light" | "dark" | "system";
|
|
2
|
+
export declare function useDashboardTheme(siteId: string, defaultTheme?: DashboardThemeMode): {
|
|
3
|
+
themeMode: DashboardThemeMode;
|
|
4
|
+
setThemeMode: (next: DashboardThemeMode) => void;
|
|
5
|
+
cycleTheme: () => void;
|
|
6
|
+
resolvedTheme: "light" | "dark";
|
|
7
|
+
};
|
|
8
|
+
export declare const DASHBOARD_THEME_LABELS: Record<DashboardThemeMode, string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
const STORAGE_PREFIX = "analytix_dashboard_theme";
|
|
4
|
+
function storageKey(siteId) {
|
|
5
|
+
return `${STORAGE_PREFIX}_${siteId}`;
|
|
6
|
+
}
|
|
7
|
+
function readStoredTheme(siteId, fallback) {
|
|
8
|
+
if (typeof window === "undefined")
|
|
9
|
+
return fallback;
|
|
10
|
+
const stored = localStorage.getItem(storageKey(siteId));
|
|
11
|
+
if (stored === "light" || stored === "dark" || stored === "system")
|
|
12
|
+
return stored;
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
export function useDashboardTheme(siteId, defaultTheme = "system") {
|
|
16
|
+
const [themeMode, setThemeModeState] = useState(defaultTheme);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setThemeModeState(readStoredTheme(siteId, defaultTheme));
|
|
19
|
+
}, [siteId, defaultTheme]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
localStorage.setItem(storageKey(siteId), themeMode);
|
|
22
|
+
}, [siteId, themeMode]);
|
|
23
|
+
function setThemeMode(next) {
|
|
24
|
+
setThemeModeState(next);
|
|
25
|
+
}
|
|
26
|
+
function cycleTheme() {
|
|
27
|
+
setThemeModeState((current) => current === "light" ? "dark" : current === "dark" ? "system" : "light");
|
|
28
|
+
}
|
|
29
|
+
const resolvedTheme = useResolvedTheme(themeMode);
|
|
30
|
+
return { themeMode, setThemeMode, cycleTheme, resolvedTheme };
|
|
31
|
+
}
|
|
32
|
+
function useResolvedTheme(mode) {
|
|
33
|
+
const [resolved, setResolved] = useState("light");
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (mode !== "system") {
|
|
36
|
+
setResolved(mode);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
40
|
+
const apply = () => setResolved(media.matches ? "dark" : "light");
|
|
41
|
+
apply();
|
|
42
|
+
media.addEventListener("change", apply);
|
|
43
|
+
return () => media.removeEventListener("change", apply);
|
|
44
|
+
}, [mode]);
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
export const DASHBOARD_THEME_LABELS = {
|
|
48
|
+
light: "Light",
|
|
49
|
+
dark: "Dark",
|
|
50
|
+
system: "System",
|
|
51
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analytix/dashboard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Analytix embeddable analytics dashboard UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"lucide-react": ">=0.400"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@analytix/core": "^0.3.
|
|
39
|
+
"@analytix/core": "^0.3.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/react": "^19",
|
package/src/dashboard.css
CHANGED
|
@@ -1,42 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embeddable dashboard styles.
|
|
3
|
+
* Host apps may override tokens via --analytix-dash-* on a parent element.
|
|
4
|
+
*/
|
|
1
5
|
.analytix-dash {
|
|
2
|
-
--ax-primary: #
|
|
3
|
-
--ax-muted: #
|
|
4
|
-
--ax-accent: #
|
|
5
|
-
--ax-surface: rgba(255, 255, 255, 0.
|
|
6
|
-
--ax-surface-solid: #ffffff;
|
|
7
|
-
--ax-border: rgba(
|
|
8
|
-
--ax-success: #
|
|
9
|
-
--ax-success-bg:
|
|
10
|
-
--ax-danger: #
|
|
11
|
-
--ax-up: #
|
|
12
|
-
--ax-down: #
|
|
13
|
-
--ax-chart-grid: rgba(
|
|
6
|
+
--ax-primary: var(--analytix-dash-ink, #111111);
|
|
7
|
+
--ax-muted: var(--analytix-dash-muted, #787774);
|
|
8
|
+
--ax-accent: var(--analytix-dash-accent, #111111);
|
|
9
|
+
--ax-surface: var(--analytix-dash-surface, rgba(255, 255, 255, 0.82));
|
|
10
|
+
--ax-surface-solid: var(--analytix-dash-surface-solid, #ffffff);
|
|
11
|
+
--ax-border: var(--analytix-dash-border, rgba(17, 17, 17, 0.08));
|
|
12
|
+
--ax-success: var(--analytix-dash-success, #346538);
|
|
13
|
+
--ax-success-bg: var(--analytix-dash-success-bg, #edf3ec);
|
|
14
|
+
--ax-danger: var(--analytix-dash-danger, #9f2f2d);
|
|
15
|
+
--ax-up: #346538;
|
|
16
|
+
--ax-down: #9f2f2d;
|
|
17
|
+
--ax-chart-grid: rgba(17, 17, 17, 0.08);
|
|
14
18
|
display: flex;
|
|
15
19
|
flex-direction: column;
|
|
16
20
|
gap: 24px;
|
|
17
21
|
width: 100%;
|
|
22
|
+
min-width: 0;
|
|
18
23
|
color: var(--ax-primary);
|
|
19
|
-
font-family:
|
|
24
|
+
font-family: var(
|
|
25
|
+
--analytix-dash-font,
|
|
26
|
+
system-ui,
|
|
27
|
+
-apple-system,
|
|
28
|
+
BlinkMacSystemFont,
|
|
29
|
+
"Segoe UI",
|
|
30
|
+
sans-serif
|
|
31
|
+
);
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
.analytix-dash.analytix-theme-dark {
|
|
23
|
-
--ax-primary: #
|
|
24
|
-
--ax-muted: #
|
|
25
|
-
--ax-accent: #
|
|
26
|
-
--ax-surface: rgba(
|
|
27
|
-
--ax-surface-solid: #
|
|
28
|
-
--ax-border: rgba(
|
|
29
|
-
--ax-success: #
|
|
30
|
-
--ax-success-bg: rgba(52,
|
|
31
|
-
--ax-
|
|
32
|
-
--ax-
|
|
33
|
-
--ax-
|
|
35
|
+
--ax-primary: var(--analytix-dash-ink, #f7f6f3);
|
|
36
|
+
--ax-muted: var(--analytix-dash-muted, #9b9a97);
|
|
37
|
+
--ax-accent: var(--analytix-dash-accent, #f7f6f3);
|
|
38
|
+
--ax-surface: var(--analytix-dash-surface, rgba(25, 25, 24, 0.88));
|
|
39
|
+
--ax-surface-solid: var(--analytix-dash-surface-solid, #191918);
|
|
40
|
+
--ax-border: var(--analytix-dash-border, rgba(255, 255, 255, 0.1));
|
|
41
|
+
--ax-success: #6ee7b7;
|
|
42
|
+
--ax-success-bg: rgba(52, 101, 56, 0.22);
|
|
43
|
+
--ax-danger: #fca5a5;
|
|
44
|
+
--ax-up: #6ee7b7;
|
|
45
|
+
--ax-down: #fca5a5;
|
|
46
|
+
--ax-chart-grid: rgba(255, 255, 255, 0.08);
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
.analytix-dash.analytix-refreshing {
|
|
37
50
|
opacity: 0.72;
|
|
38
51
|
pointer-events: none;
|
|
39
|
-
transition: opacity 0.
|
|
52
|
+
transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
.analytix-dash .filters {
|
|
@@ -45,7 +58,7 @@
|
|
|
45
58
|
gap: 14px;
|
|
46
59
|
padding: 18px;
|
|
47
60
|
border: 1px solid var(--ax-border);
|
|
48
|
-
border-radius:
|
|
61
|
+
border-radius: 12px;
|
|
49
62
|
background: var(--ax-surface);
|
|
50
63
|
}
|
|
51
64
|
|
|
@@ -83,17 +96,33 @@
|
|
|
83
96
|
font-weight: 600;
|
|
84
97
|
cursor: pointer;
|
|
85
98
|
color: var(--ax-muted);
|
|
99
|
+
transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
100
|
+
background 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
101
|
+
border-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
102
|
+
color 160ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@media (hover: hover) and (pointer: fine) {
|
|
106
|
+
.analytix-dash .chip:hover {
|
|
107
|
+
color: var(--ax-primary);
|
|
108
|
+
border-color: var(--ax-accent);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.analytix-dash .chip:active,
|
|
113
|
+
.analytix-dash .chipActive:active {
|
|
114
|
+
transform: scale(0.97);
|
|
86
115
|
}
|
|
87
116
|
|
|
88
117
|
.analytix-dash .chipActive {
|
|
89
|
-
background: rgba(
|
|
90
|
-
border-color: rgba(
|
|
118
|
+
background: rgba(17, 17, 17, 0.06);
|
|
119
|
+
border-color: rgba(17, 17, 17, 0.14);
|
|
91
120
|
color: var(--ax-accent);
|
|
92
121
|
}
|
|
93
122
|
|
|
94
123
|
.analytix-dash.analytix-theme-dark .chipActive {
|
|
95
|
-
background: rgba(
|
|
96
|
-
border-color: rgba(
|
|
124
|
+
background: rgba(255, 255, 255, 0.08);
|
|
125
|
+
border-color: rgba(255, 255, 255, 0.16);
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
.analytix-dash .dateRangeRow {
|
|
@@ -115,9 +144,17 @@
|
|
|
115
144
|
font: inherit;
|
|
116
145
|
padding: 10px 12px;
|
|
117
146
|
border: 1px solid var(--ax-border);
|
|
118
|
-
border-radius:
|
|
147
|
+
border-radius: 8px;
|
|
119
148
|
background: var(--ax-surface-solid);
|
|
120
149
|
color: var(--ax-primary);
|
|
150
|
+
transition: border-color 160ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.analytix-dash .textField input:focus,
|
|
154
|
+
.analytix-dash .dateRangeRow input:focus,
|
|
155
|
+
.analytix-dash select:focus {
|
|
156
|
+
outline: none;
|
|
157
|
+
border-color: var(--ax-accent);
|
|
121
158
|
}
|
|
122
159
|
|
|
123
160
|
.analytix-dash .checkboxRow {
|
|
@@ -139,7 +176,7 @@
|
|
|
139
176
|
flex-direction: column;
|
|
140
177
|
gap: 8px;
|
|
141
178
|
padding: 18px;
|
|
142
|
-
border-radius:
|
|
179
|
+
border-radius: 12px;
|
|
143
180
|
border: 1px solid var(--ax-border);
|
|
144
181
|
background: var(--ax-surface);
|
|
145
182
|
color: var(--ax-accent);
|
|
@@ -151,6 +188,7 @@
|
|
|
151
188
|
font-weight: 600;
|
|
152
189
|
color: var(--ax-primary);
|
|
153
190
|
line-height: 1.2;
|
|
191
|
+
font-variant-numeric: tabular-nums;
|
|
154
192
|
}
|
|
155
193
|
|
|
156
194
|
.analytix-dash .metricLabel {
|
|
@@ -173,7 +211,7 @@
|
|
|
173
211
|
|
|
174
212
|
.analytix-dash .chartPanel {
|
|
175
213
|
padding: 18px;
|
|
176
|
-
border-radius:
|
|
214
|
+
border-radius: 12px;
|
|
177
215
|
border: 1px solid var(--ax-border);
|
|
178
216
|
background: var(--ax-surface);
|
|
179
217
|
min-width: 0;
|
|
@@ -183,11 +221,15 @@
|
|
|
183
221
|
width: 100%;
|
|
184
222
|
height: 280px;
|
|
185
223
|
min-height: 280px;
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
186
227
|
}
|
|
187
228
|
|
|
188
229
|
.analytix-dash .panelTitle {
|
|
189
|
-
font-size: 0.
|
|
230
|
+
font-size: 0.75rem;
|
|
190
231
|
font-weight: 700;
|
|
232
|
+
letter-spacing: 0.06em;
|
|
191
233
|
text-transform: uppercase;
|
|
192
234
|
color: var(--ax-muted);
|
|
193
235
|
margin-bottom: 14px;
|
|
@@ -250,6 +292,7 @@
|
|
|
250
292
|
display: flex;
|
|
251
293
|
gap: 8px;
|
|
252
294
|
align-items: center;
|
|
295
|
+
flex-wrap: wrap;
|
|
253
296
|
}
|
|
254
297
|
|
|
255
298
|
.analytix-dash .btn {
|
|
@@ -265,6 +308,18 @@
|
|
|
265
308
|
font-weight: 600;
|
|
266
309
|
color: var(--ax-primary);
|
|
267
310
|
text-decoration: none;
|
|
311
|
+
transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
|
|
312
|
+
background 160ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@media (hover: hover) and (pointer: fine) {
|
|
316
|
+
.analytix-dash .btn:hover {
|
|
317
|
+
background: var(--ax-surface);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.analytix-dash .btn:active {
|
|
322
|
+
transform: scale(0.97);
|
|
268
323
|
}
|
|
269
324
|
|
|
270
325
|
.analytix-dash .btnIcon {
|
|
@@ -272,27 +327,49 @@
|
|
|
272
327
|
}
|
|
273
328
|
|
|
274
329
|
.analytix-dash .realtime {
|
|
330
|
+
display: inline-flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 8px;
|
|
275
333
|
padding: 10px 14px;
|
|
276
|
-
border-radius:
|
|
334
|
+
border-radius: 9999px;
|
|
277
335
|
background: var(--ax-success-bg);
|
|
278
336
|
color: var(--ax-success);
|
|
279
337
|
font-size: 0.875rem;
|
|
280
338
|
font-weight: 600;
|
|
281
339
|
}
|
|
282
340
|
|
|
341
|
+
.analytix-dash .realtime::before {
|
|
342
|
+
content: "";
|
|
343
|
+
width: 7px;
|
|
344
|
+
height: 7px;
|
|
345
|
+
border-radius: 50%;
|
|
346
|
+
background: currentColor;
|
|
347
|
+
opacity: 0.85;
|
|
348
|
+
}
|
|
349
|
+
|
|
283
350
|
/* Skeleton */
|
|
284
351
|
.analytix-dash.analytix-skeleton .sk {
|
|
285
352
|
border-radius: 8px;
|
|
286
353
|
background: linear-gradient(
|
|
287
354
|
90deg,
|
|
288
|
-
rgba(
|
|
289
|
-
rgba(
|
|
290
|
-
rgba(
|
|
355
|
+
rgba(120, 119, 116, 0.1) 0%,
|
|
356
|
+
rgba(120, 119, 116, 0.18) 50%,
|
|
357
|
+
rgba(120, 119, 116, 0.1) 100%
|
|
291
358
|
);
|
|
292
359
|
background-size: 200% 100%;
|
|
293
360
|
animation: analytix-shimmer 1.4s ease-in-out infinite;
|
|
294
361
|
}
|
|
295
362
|
|
|
363
|
+
.analytix-dash.analytix-theme-dark.analytix-skeleton .sk {
|
|
364
|
+
background: linear-gradient(
|
|
365
|
+
90deg,
|
|
366
|
+
rgba(255, 255, 255, 0.06) 0%,
|
|
367
|
+
rgba(255, 255, 255, 0.12) 50%,
|
|
368
|
+
rgba(255, 255, 255, 0.06) 100%
|
|
369
|
+
);
|
|
370
|
+
background-size: 200% 100%;
|
|
371
|
+
}
|
|
372
|
+
|
|
296
373
|
.analytix-dash.analytix-skeleton .skRow {
|
|
297
374
|
display: flex;
|
|
298
375
|
gap: 6px;
|
|
@@ -302,6 +379,7 @@
|
|
|
302
379
|
.analytix-dash.analytix-skeleton .skRealtime {
|
|
303
380
|
width: 260px;
|
|
304
381
|
height: 40px;
|
|
382
|
+
border-radius: 9999px;
|
|
305
383
|
}
|
|
306
384
|
|
|
307
385
|
.analytix-dash.analytix-skeleton .skBtn {
|
|
@@ -372,6 +450,17 @@
|
|
|
372
450
|
}
|
|
373
451
|
}
|
|
374
452
|
|
|
453
|
+
@media (prefers-reduced-motion: reduce) {
|
|
454
|
+
.analytix-dash.analytix-skeleton .sk {
|
|
455
|
+
animation: none;
|
|
456
|
+
background: rgba(120, 119, 116, 0.12);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.analytix-dash.analytix-theme-dark.analytix-skeleton .sk {
|
|
460
|
+
background: rgba(255, 255, 255, 0.08);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
375
464
|
@media (max-width: 900px) {
|
|
376
465
|
.analytix-dash .filters,
|
|
377
466
|
.analytix-dash .splitPanels {
|