@sybilion/uilib 1.3.18 → 1.3.20
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/esm/components/ui/Chart/Chart.styl.js +2 -2
- package/dist/esm/components/ui/Chart/components/ChartTooltipItem.js +1 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +1 -1
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +2 -1
- package/dist/esm/components/ui/InteractionOverlay/InteractionOverlay.js +9 -6
- package/dist/esm/components/ui/InteractionOverlay/InteractionOverlay.styl.js +1 -1
- package/dist/esm/contexts/chat-context.js +35 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/types/src/components/ui/InteractionOverlay/InteractionOverlay.d.ts +3 -1
- package/dist/esm/types/src/contexts/chat-context.d.ts +8 -0
- package/package.json +1 -1
- package/src/components/ui/Chart/Chart.styl +23 -4
- package/src/components/ui/Chart/Chart.styl.d.ts +1 -0
- package/src/components/ui/Chart/components/ChartTooltipItem.tsx +1 -1
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +4 -1
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +2 -0
- package/src/components/ui/InteractionOverlay/InteractionOverlay.styl +18 -3
- package/src/components/ui/InteractionOverlay/InteractionOverlay.tsx +17 -5
- package/src/contexts/chat-context.tsx +42 -0
- package/src/docs/pages/ChartAreaInteractivePage.tsx +23 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".Chart_chartContainer__--q1l{aspect-ratio:16/9;display:flex;font-size:.75rem;justify-content:center;line-height:1rem;max-width:100%;touch-action:none;width:100%}.Chart_chartContainer__--q1l .recharts-cartesian-axis-tick text{fill:var(--muted-foreground)}.Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--border)}.dark .Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--sb-slate-900)}.Chart_chartContainer__--q1l .recharts-curve.recharts-tooltip-cursor,.Chart_chartContainer__--q1l .recharts-polar-grid [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-radial-bar-background-sector,.Chart_chartContainer__--q1l .recharts-rectangle.recharts-tooltip-cursor{fill:var(--muted)}.Chart_chartContainer__--q1l .recharts-reference-line [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-dot[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-layer,.Chart_chartContainer__--q1l .recharts-sector{outline:none}.Chart_chartContainer__--q1l .recharts-sector[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-surface{outline:none}.Chart_chartContainer__--q1l .recharts-wrapper{position:relative}.Chart_chartContainer__--q1l .recharts-surface{position:relative;z-index:1}.Chart_chartContainer__--q1l .recharts-tooltip-wrapper{z-index:3!important}.Chart_chartContainer__--q1l .recharts-active-dot{z-index:3}.Chart_chartGrid__t52WF{stroke-width:.6}.Chart_tooltipContainer__6tc0q{align-items:start;box-sizing:border-box;display:grid;max-width:
|
|
4
|
-
var S = {"chartContainer":"Chart_chartContainer__--q1l","chartGrid":"Chart_chartGrid__t52WF","tooltipContainer":"Chart_tooltipContainer__6tc0q","tooltipItem":"Chart_tooltipItem__j8I9T","tooltipIndicator":"Chart_tooltipIndicator__Z-JWp","indicator-dot":"Chart_indicator-dot__MWcmW","indicator-line":"Chart_indicator-line__MO3ul","indicator-dashed":"Chart_indicator-dashed__2LqIN","nested":"Chart_nested__7EWWk","tooltipContent":"Chart_tooltipContent__M3R-W","tooltipLabel":"Chart_tooltipLabel__zMpjZ","tooltipValue":"Chart_tooltipValue__vTQxU","legendContainer":"Chart_legendContainer__u1J3U","legendItem":"Chart_legendItem__0CSyC","legendIndicator":"Chart_legendIndicator__erzzP","chart-line-blink":"Chart_chart-line-blink__4EI-g"};
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.Chart_chartContainer__--q1l{aspect-ratio:16/9;display:flex;font-size:.75rem;justify-content:center;line-height:1rem;max-width:100%;touch-action:none;width:100%}.Chart_chartContainer__--q1l .recharts-cartesian-axis-tick text{fill:var(--muted-foreground)}.Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--border)}.dark .Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--sb-slate-900)}.Chart_chartContainer__--q1l .recharts-curve.recharts-tooltip-cursor,.Chart_chartContainer__--q1l .recharts-polar-grid [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-radial-bar-background-sector,.Chart_chartContainer__--q1l .recharts-rectangle.recharts-tooltip-cursor{fill:var(--muted)}.Chart_chartContainer__--q1l .recharts-reference-line [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-dot[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-layer,.Chart_chartContainer__--q1l .recharts-sector{outline:none}.Chart_chartContainer__--q1l .recharts-sector[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-surface{outline:none}.Chart_chartContainer__--q1l .recharts-wrapper{position:relative}.Chart_chartContainer__--q1l .recharts-surface{position:relative;z-index:1}.Chart_chartContainer__--q1l .recharts-tooltip-wrapper{box-sizing:border-box;max-width:90%;z-index:3!important}.Chart_chartContainer__--q1l .recharts-active-dot{z-index:3}.Chart_chartGrid__t52WF{stroke-width:.6}.Chart_tooltipContainer__6tc0q{align-items:start;box-sizing:border-box;display:grid;max-width:min(500px,90vw);min-width:0;overflow-wrap:break-word;width:100%;word-break:break-word;grid-gap:.375rem;backdrop-filter:blur(10px);background-color:color-mix(in srgb,var(--background) 50%,transparent);border:1px solid var(--border)/.5;border-radius:.5rem;box-shadow:0 10px 10px -5px rgba(0,0,0,.2),0 0 1px 0 var(--muted-foreground);font-size:.75rem;gap:.375rem;line-height:1rem;opacity:0;padding:.375rem .625rem;transition:opacity .5s ease-out}.dark .Chart_tooltipContainer__6tc0q{box-shadow:0 0 1px 0 var(--sb-slate-700),0 10px 10px -5px rgba(0,0,0,.7)}.Chart_chartContainer__--q1l:hover .Chart_tooltipContainer__6tc0q{opacity:1;transition-duration:.5s}.Chart_tooltipItem__j8I9T{align-items:stretch;display:flex;flex-wrap:wrap;gap:.5rem;width:100%}.Chart_tooltipItem__j8I9T>svg{color:var(--muted-foreground);height:.625rem;width:.625rem}.Chart_tooltipIndicator__Z-JWp{background-color:var(--color-bg);border-color:var(--color-border);border-radius:2px;border-width:1px;flex-shrink:0}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dot__MWcmW{height:.625rem;width:.625rem}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-line__MO3ul{width:.25rem}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dashed__2LqIN{background-color:transparent;border-style:dashed;border-width:1.5px;width:0}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dashed__2LqIN.Chart_nested__7EWWk{margin-bottom:.125rem;margin-top:.125rem}.Chart_tooltipContent__M3R-W{display:flex;flex:1 1 0%;justify-content:space-between;line-height:1;min-width:0}.Chart_tooltipLabel__zMpjZ{display:grid;min-width:0;grid-gap:.375rem;gap:.375rem}.Chart_tooltipLabelText__45osJ{display:-webkit-box;-webkit-line-clamp:3;max-width:100%;min-width:0;overflow:hidden;word-break:break-word;-webkit-box-orient:vertical}.Chart_tooltipValue__vTQxU{color:var(--foreground);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:500;margin-left:var(--p-3);white-space:nowrap}.Chart_legendContainer__u1J3U{align-items:center;display:flex;gap:1rem;justify-content:center}.Chart_legendItem__0CSyC{align-items:center;display:flex;gap:.375rem}.Chart_legendItem__0CSyC>svg{color:var(--muted-foreground);height:.75rem;width:.75rem}.Chart_legendIndicator__erzzP{border-radius:2px;flex-shrink:0;height:.5rem;width:.5rem}.chart-line-blinking path{animation:chart-line-blink 1s ease-in-out infinite;animation-direction:alternate}@keyframes Chart_chart-line-blink__4EI-g{0%{opacity:.5}to{opacity:1}}";
|
|
4
|
+
var S = {"chartContainer":"Chart_chartContainer__--q1l","chartGrid":"Chart_chartGrid__t52WF","tooltipContainer":"Chart_tooltipContainer__6tc0q","tooltipItem":"Chart_tooltipItem__j8I9T","tooltipIndicator":"Chart_tooltipIndicator__Z-JWp","indicator-dot":"Chart_indicator-dot__MWcmW","indicator-line":"Chart_indicator-line__MO3ul","indicator-dashed":"Chart_indicator-dashed__2LqIN","nested":"Chart_nested__7EWWk","tooltipContent":"Chart_tooltipContent__M3R-W","tooltipLabel":"Chart_tooltipLabel__zMpjZ","tooltipLabelText":"Chart_tooltipLabelText__45osJ","tooltipValue":"Chart_tooltipValue__vTQxU","legendContainer":"Chart_legendContainer__u1J3U","legendItem":"Chart_legendItem__0CSyC","legendIndicator":"Chart_legendIndicator__erzzP","chart-line-blink":"Chart_chart-line-blink__4EI-g"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
7
7
|
export { S as default };
|
|
@@ -16,7 +16,7 @@ function ChartTooltipItem({ item, index, config, indicator, hideIndicator, nestL
|
|
|
16
16
|
return (jsx("div", { className: cn(S.tooltipItem, indicator === 'dot' && 'items-center'), children: formatter && item?.value !== undefined && item.name ? (formatter(item.value, item.name, item, index, item.payload)) : (jsxs(Fragment, { children: [itemConfig?.icon ? (jsx(itemConfig.icon, {})) : (!hideIndicator && (jsx("div", { className: cn(S.tooltipIndicator, S[`indicator-${indicator}`], nestLabel && S.nested), style: {
|
|
17
17
|
'--color-bg': indicatorColor,
|
|
18
18
|
'--color-border': indicatorColor,
|
|
19
|
-
} }))), jsxs("div", { className: cn(S.tooltipContent, nestLabel ? 'items-end' : 'items-center'), children: [jsxs("div", { className: S.tooltipLabel, children: [nestLabel ? tooltipLabel : null, jsx("span", { className:
|
|
19
|
+
} }))), jsxs("div", { className: cn(S.tooltipContent, nestLabel ? 'items-end' : 'items-center'), children: [jsxs("div", { className: S.tooltipLabel, children: [nestLabel ? tooltipLabel : null, jsx("span", { className: cn('text-muted-foreground', S.tooltipLabelText), children: itemConfig?.label || item.name })] }), item.value && (jsx("span", { className: S.tooltipValue, children: Array.isArray(item.value)
|
|
20
20
|
? `${item.value[0].toLocaleString(undefined, { maximumFractionDigits: 20 })} – ${item.value[1].toLocaleString(undefined, { maximumFractionDigits: 20 })}`
|
|
21
21
|
: item.value.toLocaleString(undefined, {
|
|
22
22
|
maximumFractionDigits: 20,
|
|
@@ -111,7 +111,7 @@ function ChartAreaInteractive({ className, chartContainerClassName, legendClassN
|
|
|
111
111
|
return (jsx(BaseChartWrapper, { ...baseChartProps, chartClassName: cn(S.chartContainer, chartContainerClassName) }));
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
|
-
return (jsxs(InteractionOverlay, { className: cn(className, loading && S.loading), children: [(!disableTimeRangeSelector || headerActions) && (jsxs("div", { className: S.chartHeaderContainer, children: [!disableTimeRangeSelector && (jsx(TimeRangeControls, { timeRange: timeRange, onTimeRangeChange: onTimeRangeChange, loading: loading })), headerActions] })), jsx(TimeRangeBrushHost, { chartData: bridgedChartData, onTimeRangeChange: onTimeRangeChange, enabled: brushEnabled, layoutKey: chartRenderId ?? null, children: renderChart() })] }));
|
|
114
|
+
return (jsxs(InteractionOverlay, { className: cn(className, loading && S.loading), disabled: loading, children: [(!disableTimeRangeSelector || headerActions) && (jsxs("div", { className: S.chartHeaderContainer, children: [!disableTimeRangeSelector && (jsx(TimeRangeControls, { timeRange: timeRange, onTimeRangeChange: onTimeRangeChange, loading: loading })), headerActions] })), jsx(TimeRangeBrushHost, { chartData: bridgedChartData, onTimeRangeChange: onTimeRangeChange, enabled: brushEnabled, layoutKey: chartRenderId ?? null, children: renderChart() })] }));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
export { ChartAreaInteractive, chartConfig };
|
|
@@ -4,7 +4,7 @@ import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js'
|
|
|
4
4
|
import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant, isPresetScriptGraph, branchesFromPresetScriptGraph } from '../ChatMessage/presetScript.js';
|
|
5
5
|
import { buildChatSendMessagePayload, displayTextFromSendPayload } from '../buildChatSendMessagePayload.js';
|
|
6
6
|
import { usedPresetIdsFromMessages, formatChatTranscript } from '../chat-preset-utils.js';
|
|
7
|
-
import { useChatsForScopeId, useChat, useChatOutboundPending, isChatEmpty } from '../../../../contexts/chat-context.js';
|
|
7
|
+
import { useChatsForScopeId, useChat, useChatOutboundPending, useSyncChatPanelBusy, isChatEmpty } from '../../../../contexts/chat-context.js';
|
|
8
8
|
import useEvent from '../../../../hooks/useEvent.js';
|
|
9
9
|
import { useIsMobile } from '../../../../hooks/useIsMobile.js';
|
|
10
10
|
import { useQueryParams } from '../../../../hooks/useQueryParams.js';
|
|
@@ -31,6 +31,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
31
31
|
const chat = useChat(effectiveScopeId, currentChatId);
|
|
32
32
|
const isOutboundPending = useChatOutboundPending(effectiveScopeId, currentChatId);
|
|
33
33
|
const isLoading = isOutboundPending || localUiBusy;
|
|
34
|
+
useSyncChatPanelBusy(isLoading);
|
|
34
35
|
const { searchParams, addSearchParams, removeSearchParams, mutateSearchParams, } = useQueryParams();
|
|
35
36
|
const chatOpen = searchParams.has(CHAT_QUERY_PARAM);
|
|
36
37
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import cn from 'classnames';
|
|
3
3
|
import { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { useIsChatLoading } from '../../../contexts/chat-context.js';
|
|
4
5
|
import { useIsTouchDevice } from '../../../hooks/useIsTouchDevice.js';
|
|
5
6
|
import S from './InteractionOverlay.styl.js';
|
|
6
7
|
|
|
7
|
-
function InteractionOverlay({ className, children, message = 'Touch to interact', }) {
|
|
8
|
+
function InteractionOverlay({ className, children, message = 'Touch to interact', disabled = false, }) {
|
|
8
9
|
const isTouchDevice = useIsTouchDevice();
|
|
10
|
+
const isChatLoading = useIsChatLoading();
|
|
11
|
+
const suppressOverlay = disabled || isChatLoading;
|
|
9
12
|
const [isVisible, setIsVisible] = useState(true);
|
|
10
13
|
const handleClick = useCallback(() => {
|
|
11
14
|
setIsVisible(false);
|
|
12
15
|
}, []);
|
|
13
16
|
useEffect(() => {
|
|
14
|
-
if (isTouchDevice) {
|
|
17
|
+
if (isTouchDevice && !suppressOverlay) {
|
|
15
18
|
setIsVisible(true);
|
|
16
19
|
}
|
|
17
|
-
}, [isTouchDevice]);
|
|
20
|
+
}, [isTouchDevice, suppressOverlay]);
|
|
18
21
|
useEffect(() => {
|
|
19
|
-
if (!isTouchDevice)
|
|
22
|
+
if (!isTouchDevice || suppressOverlay)
|
|
20
23
|
return;
|
|
21
24
|
const handleScroll = () => {
|
|
22
25
|
setIsVisible(true);
|
|
@@ -25,11 +28,11 @@ function InteractionOverlay({ className, children, message = 'Touch to interact'
|
|
|
25
28
|
return () => {
|
|
26
29
|
document.removeEventListener('scroll', handleScroll, { capture: true });
|
|
27
30
|
};
|
|
28
|
-
}, [isTouchDevice]);
|
|
31
|
+
}, [isTouchDevice, suppressOverlay]);
|
|
29
32
|
if (!isTouchDevice) {
|
|
30
33
|
return jsx(Fragment, { children: children });
|
|
31
34
|
}
|
|
32
|
-
return (jsxs("div", { className: cn(S.root, !isVisible && S.hidden, className), children: [children, jsx("button", { type: "button", className: S.overlay, onClick: handleClick, "aria-label": message, children: jsx("span", { className: S.message, children: message }) })] }));
|
|
35
|
+
return (jsxs("div", { className: cn(S.root, (!isVisible || suppressOverlay) && S.hidden, className), children: [children, jsx("button", { type: "button", className: S.overlay, onClick: handleClick, "aria-label": message, children: jsx("span", { className: S.message, children: message }) })] }));
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export { InteractionOverlay };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".InteractionOverlay_root__9FV58{position:relative}.InteractionOverlay_overlay__Nwawr{background:transparent;border:none;box-shadow:inset 0 0 80px 40px var(--overlay-background-color);
|
|
3
|
+
var css_248z = ".InteractionOverlay_root__9FV58{position:relative}.InteractionOverlay_overlay__Nwawr{background:transparent;border:none;cursor:pointer;inset:0;margin:0;padding:0;position:absolute;touch-action:manipulation;z-index:4}.InteractionOverlay_overlay__Nwawr:before{background-color:color-mix(in srgb,var(--overlay-background-color) 70%,transparent);bottom:calc(var(--p-2)*-1);box-shadow:inset 0 0 80px 40px var(--overlay-background-color);content:\"\";left:calc(var(--p-2)*-1);pointer-events:none;position:absolute;right:calc(var(--p-2)*-1);top:calc(var(--p-2)*-1);transition:.2s ease-out;transition-property:box-shadow,background-color;z-index:-1}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_overlay__Nwawr{pointer-events:none}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_overlay__Nwawr:before{background-color:transparent;box-shadow:inset 0 0 0 0 var(--overlay-background-color)}.InteractionOverlay_message__oCuPR{backdrop-filter:blur(10px);background-color:color-mix(in srgb,var(--overlay-background-color) 80%,transparent);border:1px solid color-mix(in srgb,var(--foreground) 14%,transparent);border-radius:9999px;box-shadow:0 0 24px 10px var(--overlay-background-color),0 0 0 1px color-mix(in srgb,var(--overlay-background-color) 40%,transparent);color:var(--foreground);font-size:.875rem;font-weight:500;left:50%;line-height:1.25;padding:var(--p-3) var(--p-5);pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);transition:.2s ease-out;transition-property:transform,opacity;white-space:nowrap;z-index:1}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_message__oCuPR{opacity:0;transform:translate(-50%,-50%) scale(.3)}:root{--overlay-background-color:var(--page-color)}";
|
|
4
4
|
var S = {"root":"InteractionOverlay_root__9FV58","overlay":"InteractionOverlay_overlay__Nwawr","hidden":"InteractionOverlay_hidden__NRlgG","message":"InteractionOverlay_message__oCuPR"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -92,6 +92,13 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
92
92
|
return loadChatsFromLS(userSwitchKey).currentChatId;
|
|
93
93
|
});
|
|
94
94
|
const [outboundPendingByKey, setOutboundPendingByKey] = useState({});
|
|
95
|
+
const [panelBusyCount, setPanelBusyCount] = useState(0);
|
|
96
|
+
const acquirePanelBusy = useCallback(() => {
|
|
97
|
+
setPanelBusyCount(count => count + 1);
|
|
98
|
+
}, []);
|
|
99
|
+
const releasePanelBusy = useCallback(() => {
|
|
100
|
+
setPanelBusyCount(count => Math.max(0, count - 1));
|
|
101
|
+
}, []);
|
|
95
102
|
const beginOutboundPending = useCallback((scopeId, chatSessionId) => {
|
|
96
103
|
const key = outboundPendingKey(scopeId, chatSessionId);
|
|
97
104
|
setOutboundPendingByKey(prev => ({
|
|
@@ -282,6 +289,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
282
289
|
setChats({});
|
|
283
290
|
setCurrentChatIdState({});
|
|
284
291
|
setOutboundPendingByKey({});
|
|
292
|
+
setPanelBusyCount(0);
|
|
285
293
|
return;
|
|
286
294
|
}
|
|
287
295
|
const loaded = loadChatsFromLS(userSwitchKey);
|
|
@@ -306,6 +314,9 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
306
314
|
getCurrentChatId,
|
|
307
315
|
deleteChat,
|
|
308
316
|
outboundPendingByKey,
|
|
317
|
+
panelBusyCount,
|
|
318
|
+
acquirePanelBusy,
|
|
319
|
+
releasePanelBusy,
|
|
309
320
|
}, children: children }));
|
|
310
321
|
}
|
|
311
322
|
const isChatEmpty = (chat) => chat?.messages.length === 0;
|
|
@@ -334,6 +345,29 @@ function useChatOutboundPending(scopeId, chatSessionId) {
|
|
|
334
345
|
return (outboundPendingByKey[key] ?? 0) > 0;
|
|
335
346
|
}, [scopeId, chatSessionId, outboundPendingByKey]);
|
|
336
347
|
}
|
|
348
|
+
/** True when chat is waiting on API or panel UI busy (safe outside ChatProvider). */
|
|
349
|
+
function useIsChatLoading() {
|
|
350
|
+
const context = useContext(ChatContext);
|
|
351
|
+
return useMemo(() => {
|
|
352
|
+
if (!context)
|
|
353
|
+
return false;
|
|
354
|
+
const outboundPending = Object.values(context.outboundPendingByKey).some(count => count > 0);
|
|
355
|
+
return outboundPending || context.panelBusyCount > 0;
|
|
356
|
+
}, [context?.outboundPendingByKey, context?.panelBusyCount]);
|
|
357
|
+
}
|
|
358
|
+
/** Syncs chat panel `isLoading` into context for touch-overlay suppression. */
|
|
359
|
+
function useSyncChatPanelBusy(isLoading) {
|
|
360
|
+
const acquirePanelBusy = useContext(ChatContext)?.acquirePanelBusy;
|
|
361
|
+
const releasePanelBusy = useContext(ChatContext)?.releasePanelBusy;
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
if (!isLoading || !acquirePanelBusy || !releasePanelBusy)
|
|
364
|
+
return;
|
|
365
|
+
acquirePanelBusy();
|
|
366
|
+
return () => {
|
|
367
|
+
releasePanelBusy();
|
|
368
|
+
};
|
|
369
|
+
}, [isLoading, acquirePanelBusy, releasePanelBusy]);
|
|
370
|
+
}
|
|
337
371
|
function useChatsForScopeId(scopeId) {
|
|
338
372
|
const { getChatsForScopeId, getCurrentChatId, setCurrentChatId, newChat, addMessage, removeMessageById, sendMessage, deleteChat, } = useChats();
|
|
339
373
|
const chats = getChatsForScopeId(scopeId);
|
|
@@ -363,4 +397,4 @@ function useCurrentChat(scopeId) {
|
|
|
363
397
|
return useChat(scopeId, chatId ?? undefined);
|
|
364
398
|
}
|
|
365
399
|
|
|
366
|
-
export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat };
|
|
400
|
+
export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat, useIsChatLoading, useSyncChatPanelBusy };
|
package/dist/esm/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme.js';
|
|
|
6
6
|
export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromSdk, sybilionApiFetch, useSybilionApiFetch, useSybilionAuth } from './sybilion-auth/SybilionAuthProvider.js';
|
|
7
7
|
export { SYBILION_AUTH_LOGIN_PATH, normalizeApiBaseUrl } from './sybilion-auth/authPaths.js';
|
|
8
8
|
export { exchangeAuth0AccessTokenForSybilionJwt } from './sybilion-auth/exchangeSybilionToken.js';
|
|
9
|
-
export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat } from './contexts/chat-context.js';
|
|
9
|
+
export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat, useIsChatLoading, useSyncChatPanelBusy } from './contexts/chat-context.js';
|
|
10
10
|
export { AnalysesSelector } from './components/ui/AnalysesSelector/AnalysesSelector.js';
|
|
11
11
|
export { AnalysisLineIcon } from './components/ui/AnalysisLineIcon/AnalysisLineIcon.js';
|
|
12
12
|
export { AppHeaderHost, AppHeaderPortal } from './components/ui/AppHeader/AppHeader.js';
|
|
@@ -2,6 +2,8 @@ interface InteractionOverlayProps {
|
|
|
2
2
|
className?: string;
|
|
3
3
|
children?: React.ReactNode;
|
|
4
4
|
message?: string;
|
|
5
|
+
/** When true, hides the touch overlay (e.g. chart data loading). */
|
|
6
|
+
disabled?: boolean;
|
|
5
7
|
}
|
|
6
|
-
export declare function InteractionOverlay({ className, children, message, }: InteractionOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function InteractionOverlay({ className, children, message, disabled, }: InteractionOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
7
9
|
export {};
|
|
@@ -21,6 +21,10 @@ export interface ChatContextType {
|
|
|
21
21
|
* `outboundPendingKey(scopeId, chatSessionId)` (session id at send start).
|
|
22
22
|
*/
|
|
23
23
|
outboundPendingByKey: Readonly<Record<string, number>>;
|
|
24
|
+
/** Ref-count of chat panels reporting UI busy (outbound wait, preset scripts, etc.). */
|
|
25
|
+
panelBusyCount: number;
|
|
26
|
+
acquirePanelBusy: () => void;
|
|
27
|
+
releasePanelBusy: () => void;
|
|
24
28
|
}
|
|
25
29
|
declare const ChatContext: import("react").Context<ChatContextType>;
|
|
26
30
|
/** Stable composite key; avoids collisions if `scopeId` contains `:`. */
|
|
@@ -36,6 +40,10 @@ export declare const isChatEmpty: (chat: Chat | null) => boolean;
|
|
|
36
40
|
export declare function useChats(): ChatContextType;
|
|
37
41
|
export declare function useChat(scopeId: string | undefined, chatId: string | undefined): Chat | null;
|
|
38
42
|
export declare function useChatOutboundPending(scopeId: string | undefined | null, chatSessionId: string | null | undefined): boolean;
|
|
43
|
+
/** True when chat is waiting on API or panel UI busy (safe outside ChatProvider). */
|
|
44
|
+
export declare function useIsChatLoading(): boolean;
|
|
45
|
+
/** Syncs chat panel `isLoading` into context for touch-overlay suppression. */
|
|
46
|
+
export declare function useSyncChatPanelBusy(isLoading: boolean): void;
|
|
39
47
|
export declare function useChatsForScopeId(scopeId: string): {
|
|
40
48
|
chats: Chat[];
|
|
41
49
|
currentChat: Chat;
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import 'lib/theme.styl'
|
|
2
|
+
|
|
1
3
|
// Chart container
|
|
2
4
|
.chartContainer
|
|
3
5
|
display flex
|
|
@@ -60,6 +62,8 @@
|
|
|
60
62
|
// Tooltip portal positions with absolute + transform; do not override with relative.
|
|
61
63
|
.recharts-tooltip-wrapper
|
|
62
64
|
z-index 3 !important
|
|
65
|
+
max-width 90%
|
|
66
|
+
box-sizing border-box
|
|
63
67
|
|
|
64
68
|
// Active dots and cursor
|
|
65
69
|
.recharts-active-dot
|
|
@@ -75,8 +79,8 @@
|
|
|
75
79
|
.tooltipContainer
|
|
76
80
|
display grid
|
|
77
81
|
box-sizing border-box
|
|
78
|
-
width
|
|
79
|
-
max-width
|
|
82
|
+
width 100%
|
|
83
|
+
max-width unquote('min(500px, 90vw)')
|
|
80
84
|
min-width 0
|
|
81
85
|
overflow-wrap break-word
|
|
82
86
|
word-break break-word
|
|
@@ -85,13 +89,18 @@
|
|
|
85
89
|
padding 0.375rem 0.625rem /* py-1.5 px-2.5 */
|
|
86
90
|
border-radius 0.5rem /* rounded-lg */
|
|
87
91
|
border 1px solid var(--border) / 0.5
|
|
88
|
-
background-color var(--background)
|
|
92
|
+
// background-color var(--background)
|
|
93
|
+
backdrop-filter blur(10px)
|
|
94
|
+
background-color unquote('color-mix(in srgb, var(--background) 50%, transparent)')
|
|
89
95
|
font-size 0.75rem /* text-xs */
|
|
90
96
|
line-height 1rem
|
|
91
|
-
box-shadow 0 10px 10px -5px rgba(0 0 0 0.
|
|
97
|
+
box-shadow 0 10px 10px -5px rgba(0 0 0 0.2), 0 0 1px 0 var(--muted-foreground)
|
|
92
98
|
opacity 0
|
|
93
99
|
transition opacity 0.5s ease-out
|
|
94
100
|
|
|
101
|
+
:global(.dark) &
|
|
102
|
+
box-shadow 0 0 1px 0 var(--sb-slate-700), 0 10px 10px -5px rgba(0 0 0 0.7)
|
|
103
|
+
|
|
95
104
|
.chartContainer:hover &
|
|
96
105
|
opacity 1
|
|
97
106
|
transition-duration 0.5s
|
|
@@ -135,19 +144,29 @@
|
|
|
135
144
|
.tooltipContent
|
|
136
145
|
display flex
|
|
137
146
|
flex 1 1 0%
|
|
147
|
+
min-width 0
|
|
138
148
|
justify-content space-between
|
|
139
149
|
line-height 1
|
|
140
150
|
|
|
141
151
|
.tooltipLabel
|
|
142
152
|
display grid
|
|
153
|
+
min-width 0
|
|
143
154
|
gap 0.375rem /* gap-1.5 */
|
|
144
155
|
|
|
156
|
+
.tooltipLabelText
|
|
157
|
+
min-width 0
|
|
158
|
+
max-width 100%
|
|
159
|
+
overflow hidden
|
|
160
|
+
word-break break-word
|
|
161
|
+
line-clamp(3)
|
|
162
|
+
|
|
145
163
|
.tooltipValue
|
|
146
164
|
font-family ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace
|
|
147
165
|
font-weight 500
|
|
148
166
|
font-variant-numeric tabular-nums
|
|
149
167
|
color var(--foreground)
|
|
150
168
|
margin-left var(--p-3)
|
|
169
|
+
white-space nowrap
|
|
151
170
|
|
|
152
171
|
// Legend styles
|
|
153
172
|
.legendContainer
|
|
@@ -92,7 +92,7 @@ export function ChartTooltipItem({
|
|
|
92
92
|
>
|
|
93
93
|
<div className={S.tooltipLabel}>
|
|
94
94
|
{nestLabel ? tooltipLabel : null}
|
|
95
|
-
<span className=
|
|
95
|
+
<span className={cn('text-muted-foreground', S.tooltipLabelText)}>
|
|
96
96
|
{itemConfig?.label || item.name}
|
|
97
97
|
</span>
|
|
98
98
|
</div>
|
|
@@ -231,7 +231,10 @@ export function ChartAreaInteractive({
|
|
|
231
231
|
};
|
|
232
232
|
|
|
233
233
|
return (
|
|
234
|
-
<InteractionOverlay
|
|
234
|
+
<InteractionOverlay
|
|
235
|
+
className={cn(className, loading && S.loading)}
|
|
236
|
+
disabled={loading}
|
|
237
|
+
>
|
|
235
238
|
{(!disableTimeRangeSelector || headerActions) && (
|
|
236
239
|
<div className={S.chartHeaderContainer}>
|
|
237
240
|
{!disableTimeRangeSelector && (
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
useChat,
|
|
33
33
|
useChatOutboundPending,
|
|
34
34
|
useChatsForScopeId,
|
|
35
|
+
useSyncChatPanelBusy,
|
|
35
36
|
} from '#uilib/contexts/chat-context';
|
|
36
37
|
import useEvent from '#uilib/hooks/useEvent';
|
|
37
38
|
import { useIsMobile } from '#uilib/hooks/useIsMobile';
|
|
@@ -144,6 +145,7 @@ export function useChatPanelChromeModel({
|
|
|
144
145
|
currentChatId,
|
|
145
146
|
);
|
|
146
147
|
const isLoading = isOutboundPending || localUiBusy;
|
|
148
|
+
useSyncChatPanelBusy(isLoading);
|
|
147
149
|
|
|
148
150
|
const {
|
|
149
151
|
searchParams,
|
|
@@ -11,13 +11,28 @@
|
|
|
11
11
|
cursor pointer
|
|
12
12
|
touch-action manipulation
|
|
13
13
|
background transparent
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
&::before
|
|
16
|
+
content ''
|
|
17
|
+
position absolute
|
|
18
|
+
top calc(-1 * var(--p-2))
|
|
19
|
+
right calc(-1 * var(--p-2))
|
|
20
|
+
bottom calc(-1 * var(--p-2))
|
|
21
|
+
left calc(-1 * var(--p-2))
|
|
22
|
+
background-color unquote('color-mix(in srgb, var(--overlay-background-color) 70%, transparent)')
|
|
23
|
+
box-shadow inset 0 0 80px 40px var(--overlay-background-color)
|
|
24
|
+
pointer-events none
|
|
25
|
+
z-index -1
|
|
26
|
+
transition 200ms ease-out
|
|
27
|
+
transition-property box-shadow, background-color
|
|
16
28
|
|
|
17
29
|
.hidden &
|
|
18
|
-
box-shadow inset 0 0 0 0 var(--overlay-background-color)
|
|
19
30
|
pointer-events none
|
|
20
31
|
|
|
32
|
+
&::before
|
|
33
|
+
box-shadow inset 0 0 0 0 var(--overlay-background-color)
|
|
34
|
+
background-color transparent
|
|
35
|
+
|
|
21
36
|
.message
|
|
22
37
|
position absolute
|
|
23
38
|
top 50%
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
2
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
|
|
4
|
+
import { useIsChatLoading } from '#uilib/contexts/chat-context';
|
|
4
5
|
import { useIsTouchDevice } from '#uilib/hooks/useIsTouchDevice';
|
|
5
6
|
|
|
6
7
|
import S from './InteractionOverlay.styl';
|
|
@@ -9,14 +10,19 @@ interface InteractionOverlayProps {
|
|
|
9
10
|
className?: string;
|
|
10
11
|
children?: React.ReactNode;
|
|
11
12
|
message?: string;
|
|
13
|
+
/** When true, hides the touch overlay (e.g. chart data loading). */
|
|
14
|
+
disabled?: boolean;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export function InteractionOverlay({
|
|
15
18
|
className,
|
|
16
19
|
children,
|
|
17
20
|
message = 'Touch to interact',
|
|
21
|
+
disabled = false,
|
|
18
22
|
}: InteractionOverlayProps) {
|
|
19
23
|
const isTouchDevice = useIsTouchDevice();
|
|
24
|
+
const isChatLoading = useIsChatLoading();
|
|
25
|
+
const suppressOverlay = disabled || isChatLoading;
|
|
20
26
|
const [isVisible, setIsVisible] = useState(true);
|
|
21
27
|
|
|
22
28
|
const handleClick = useCallback(() => {
|
|
@@ -24,13 +30,13 @@ export function InteractionOverlay({
|
|
|
24
30
|
}, []);
|
|
25
31
|
|
|
26
32
|
useEffect(() => {
|
|
27
|
-
if (isTouchDevice) {
|
|
33
|
+
if (isTouchDevice && !suppressOverlay) {
|
|
28
34
|
setIsVisible(true);
|
|
29
35
|
}
|
|
30
|
-
}, [isTouchDevice]);
|
|
36
|
+
}, [isTouchDevice, suppressOverlay]);
|
|
31
37
|
|
|
32
38
|
useEffect(() => {
|
|
33
|
-
if (!isTouchDevice) return;
|
|
39
|
+
if (!isTouchDevice || suppressOverlay) return;
|
|
34
40
|
|
|
35
41
|
const handleScroll = () => {
|
|
36
42
|
setIsVisible(true);
|
|
@@ -40,14 +46,20 @@ export function InteractionOverlay({
|
|
|
40
46
|
return () => {
|
|
41
47
|
document.removeEventListener('scroll', handleScroll, { capture: true });
|
|
42
48
|
};
|
|
43
|
-
}, [isTouchDevice]);
|
|
49
|
+
}, [isTouchDevice, suppressOverlay]);
|
|
44
50
|
|
|
45
51
|
if (!isTouchDevice) {
|
|
46
52
|
return <>{children}</>;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
return (
|
|
50
|
-
<div
|
|
56
|
+
<div
|
|
57
|
+
className={cn(
|
|
58
|
+
S.root,
|
|
59
|
+
(!isVisible || suppressOverlay) && S.hidden,
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
51
63
|
{children}
|
|
52
64
|
<button
|
|
53
65
|
type="button"
|
|
@@ -66,6 +66,10 @@ export interface ChatContextType {
|
|
|
66
66
|
* `outboundPendingKey(scopeId, chatSessionId)` (session id at send start).
|
|
67
67
|
*/
|
|
68
68
|
outboundPendingByKey: Readonly<Record<string, number>>;
|
|
69
|
+
/** Ref-count of chat panels reporting UI busy (outbound wait, preset scripts, etc.). */
|
|
70
|
+
panelBusyCount: number;
|
|
71
|
+
acquirePanelBusy: () => void;
|
|
72
|
+
releasePanelBusy: () => void;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
|
@@ -175,6 +179,15 @@ export function ChatProvider({
|
|
|
175
179
|
const [outboundPendingByKey, setOutboundPendingByKey] = useState<
|
|
176
180
|
Record<string, number>
|
|
177
181
|
>({});
|
|
182
|
+
const [panelBusyCount, setPanelBusyCount] = useState(0);
|
|
183
|
+
|
|
184
|
+
const acquirePanelBusy = useCallback(() => {
|
|
185
|
+
setPanelBusyCount(count => count + 1);
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
const releasePanelBusy = useCallback(() => {
|
|
189
|
+
setPanelBusyCount(count => Math.max(0, count - 1));
|
|
190
|
+
}, []);
|
|
178
191
|
|
|
179
192
|
const beginOutboundPending = useCallback(
|
|
180
193
|
(scopeId: string, chatSessionId: string) => {
|
|
@@ -444,6 +457,7 @@ export function ChatProvider({
|
|
|
444
457
|
setChats({});
|
|
445
458
|
setCurrentChatIdState({});
|
|
446
459
|
setOutboundPendingByKey({});
|
|
460
|
+
setPanelBusyCount(0);
|
|
447
461
|
return;
|
|
448
462
|
}
|
|
449
463
|
|
|
@@ -475,6 +489,9 @@ export function ChatProvider({
|
|
|
475
489
|
getCurrentChatId,
|
|
476
490
|
deleteChat,
|
|
477
491
|
outboundPendingByKey,
|
|
492
|
+
panelBusyCount,
|
|
493
|
+
acquirePanelBusy,
|
|
494
|
+
releasePanelBusy,
|
|
478
495
|
}}
|
|
479
496
|
>
|
|
480
497
|
{children}
|
|
@@ -519,6 +536,31 @@ export function useChatOutboundPending(
|
|
|
519
536
|
}, [scopeId, chatSessionId, outboundPendingByKey]);
|
|
520
537
|
}
|
|
521
538
|
|
|
539
|
+
/** True when chat is waiting on API or panel UI busy (safe outside ChatProvider). */
|
|
540
|
+
export function useIsChatLoading(): boolean {
|
|
541
|
+
const context = useContext(ChatContext);
|
|
542
|
+
return useMemo(() => {
|
|
543
|
+
if (!context) return false;
|
|
544
|
+
const outboundPending = Object.values(context.outboundPendingByKey).some(
|
|
545
|
+
count => count > 0,
|
|
546
|
+
);
|
|
547
|
+
return outboundPending || context.panelBusyCount > 0;
|
|
548
|
+
}, [context?.outboundPendingByKey, context?.panelBusyCount]);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Syncs chat panel `isLoading` into context for touch-overlay suppression. */
|
|
552
|
+
export function useSyncChatPanelBusy(isLoading: boolean): void {
|
|
553
|
+
const acquirePanelBusy = useContext(ChatContext)?.acquirePanelBusy;
|
|
554
|
+
const releasePanelBusy = useContext(ChatContext)?.releasePanelBusy;
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
if (!isLoading || !acquirePanelBusy || !releasePanelBusy) return;
|
|
557
|
+
acquirePanelBusy();
|
|
558
|
+
return () => {
|
|
559
|
+
releasePanelBusy();
|
|
560
|
+
};
|
|
561
|
+
}, [isLoading, acquirePanelBusy, releasePanelBusy]);
|
|
562
|
+
}
|
|
563
|
+
|
|
522
564
|
export function useChatsForScopeId(scopeId: string) {
|
|
523
565
|
const {
|
|
524
566
|
getChatsForScopeId,
|
|
@@ -6,7 +6,9 @@ import type {
|
|
|
6
6
|
OverlayMode,
|
|
7
7
|
} from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
8
8
|
import { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
|
|
9
|
+
import { Label } from '#uilib/components/ui/Label';
|
|
9
10
|
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
11
|
+
import { Switch } from '#uilib/components/ui/Switch';
|
|
10
12
|
import { Tabs, TabsList, TabsTrigger } from '#uilib/components/ui/Tabs';
|
|
11
13
|
import { useTheme } from '#uilib/contexts/theme-context';
|
|
12
14
|
import type { ForecastData } from '#uilib/types/forecast-data';
|
|
@@ -83,7 +85,10 @@ const INITIAL_CHART: ChartDataPoint[] = [
|
|
|
83
85
|
];
|
|
84
86
|
|
|
85
87
|
const DEMO_FORECAST_ITEMS: ForecastItemData[] = [
|
|
86
|
-
{
|
|
88
|
+
{
|
|
89
|
+
id: DEMO_FORECAST_ID,
|
|
90
|
+
name: 'My custom forecast with an extremely long descriptive name that should wrap and clamp inside the chart tooltip after three lines',
|
|
91
|
+
},
|
|
87
92
|
];
|
|
88
93
|
|
|
89
94
|
type DemoMode = 'none' | OverlayMode;
|
|
@@ -102,6 +107,7 @@ export default function ChartAreaInteractivePage() {
|
|
|
102
107
|
string | null
|
|
103
108
|
>('0.9');
|
|
104
109
|
const [upperThreshold, setUpperThreshold] = useState(15);
|
|
110
|
+
const [loading, setLoading] = useState(false);
|
|
105
111
|
|
|
106
112
|
const toggleLegendSeries = useCallback((key: string) => {
|
|
107
113
|
setHidden(prev => {
|
|
@@ -155,6 +161,21 @@ export default function ChartAreaInteractivePage() {
|
|
|
155
161
|
<TabsTrigger value="thresholds">Thresholds</TabsTrigger>
|
|
156
162
|
</TabsList>
|
|
157
163
|
</Tabs>
|
|
164
|
+
<div
|
|
165
|
+
style={{
|
|
166
|
+
display: 'flex',
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
gap: 8,
|
|
169
|
+
marginBottom: 16,
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<Switch
|
|
173
|
+
id="chart-loading"
|
|
174
|
+
checked={loading}
|
|
175
|
+
onCheckedChange={setLoading}
|
|
176
|
+
/>
|
|
177
|
+
<Label htmlFor="chart-loading">Loading</Label>
|
|
178
|
+
</div>
|
|
158
179
|
<ChartAreaInteractive
|
|
159
180
|
timeRange={timeRange}
|
|
160
181
|
onTimeRangeChange={setTimeRange}
|
|
@@ -163,7 +184,7 @@ export default function ChartAreaInteractivePage() {
|
|
|
163
184
|
mode={mode}
|
|
164
185
|
chartData={chartData}
|
|
165
186
|
forecastData={DEMO_FORECAST_ITEMS}
|
|
166
|
-
loading={
|
|
187
|
+
loading={loading}
|
|
167
188
|
isDarkTheme={isDarkMode}
|
|
168
189
|
toggleLegendSeries={toggleLegendSeries}
|
|
169
190
|
ensureAnalysisSeriesVisible={ensureAnalysisSeriesVisible}
|