@sybilion/uilib 1.3.41 → 1.3.44
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/Chat/ChatPrompt/ChatPrompt.js +2 -2
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPromptComposer.js +2 -2
- package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +24 -17
- package/dist/esm/components/ui/Page/PageHeader/PageHeader.js +1 -1
- package/dist/esm/components/ui/RegionCoords/RegionSelector.js +2 -2
- package/dist/esm/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.js +23 -1
- package/dist/esm/components/widgets/DriverCard/DriverCard.js +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.d.ts +1 -3
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.d.ts +0 -2
- package/dist/esm/types/src/components/ui/RegionCoords/RegionSelector.types.d.ts +1 -1
- package/dist/esm/types/src/docs/pages/ChatSheetPage.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/ui/ChartAreaInteractive/AGENT.md +19 -0
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +1 -1
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +9 -11
- package/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.tsx +2 -9
- package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +34 -21
- package/src/components/ui/Page/PageHeader/PageHeader.tsx +1 -1
- package/src/components/ui/RegionCoords/RegionSelector.tsx +20 -18
- package/src/components/ui/RegionCoords/RegionSelector.types.ts +1 -1
- package/src/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.tsx +35 -4
- package/src/components/widgets/AGENT.md +21 -0
- package/src/components/widgets/DriverCard/DriverCard.tsx +5 -1
- package/src/components/widgets/DriverMap/AGENT.md +18 -0
- package/src/components/widgets/DriversComparisonChart/AGENT.md +18 -0
- package/src/components/widgets/PerformanceChart/AGENT.md +18 -0
- package/src/components/widgets/SidebarDatasetsItemsGrouped/AGENT.md +18 -0
- package/src/docs/pages/ChatSheetPage.tsx +61 -0
- package/src/docs/pages/TooltipPage.tsx +0 -31
- package/src/docs/registry.ts +6 -0
|
@@ -9,7 +9,7 @@ import { useChatPromptEditor } from './useChatPromptEditor.js';
|
|
|
9
9
|
function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, slashCommandItems, onSlashItemCommand, attachments = [], onRemoveAttachment, disabled = false, attachmentAccept, onAttachmentFiles, }) {
|
|
10
10
|
const attachmentsCount = attachments.length;
|
|
11
11
|
const emitSubmitRef = useRef(() => { });
|
|
12
|
-
const { editor, trimmedMessage, resetAfterSend
|
|
12
|
+
const { editor, trimmedMessage, resetAfterSend } = useChatPromptEditor({
|
|
13
13
|
disabled,
|
|
14
14
|
placeholder,
|
|
15
15
|
slashCommandItems,
|
|
@@ -42,7 +42,7 @@ function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage,
|
|
|
42
42
|
if (!editor) {
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
-
return (jsxs("form", { onSubmit: handleSubmitForm, className: cn(S.root, className), children: [jsx(ChatPromptAttachments, { attachments: attachments, onRemove: index => onRemoveAttachment?.(index), disabled: disabled }), jsx(ChatPromptComposer, { editor: editor, disabled: disabled, trimmedMessage: trimmedMessage, attachments: attachments, attachmentAccept: attachmentAccept, onAttachmentFiles: onAttachmentFiles
|
|
45
|
+
return (jsxs("form", { onSubmit: handleSubmitForm, className: cn(S.root, className), children: [jsx(ChatPromptAttachments, { attachments: attachments, onRemove: index => onRemoveAttachment?.(index), disabled: disabled }), jsx(ChatPromptComposer, { editor: editor, disabled: disabled, trimmedMessage: trimmedMessage, attachments: attachments, attachmentAccept: attachmentAccept, onAttachmentFiles: onAttachmentFiles }), footer] }));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export { ChatPrompt };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_fileInput__xdgPn{display:none}.ChatPrompt_attachButton__gi-qF{align-self:flex-end;flex-shrink:0}.ChatPrompt_editorWrap__Q7gat{align-self:stretch;flex:1;min-width:0}.ChatPrompt_editorMount__Phh4D{background:transparent;border:none;border-radius:0!important;box-shadow:none!important;display:flex;flex:1;flex-direction:column;max-height:200px;min-height:40px;min-width:0;padding:0!important}.ChatPrompt_editorMount__Phh4D:focus-within{box-shadow:none!important}.ChatPrompt_editorMount__Phh4D .ProseMirror{border:none!important;box-shadow:none!important;flex:1;margin:0;max-height:200px!important;min-height:40px!important;outline:none!important;overflow-x:hidden!important;overflow-y:auto!important;padding:var(--p-2) 0 0!important;resize:none!important;white-space:pre-wrap;word-break:break-word}.ChatPrompt_editorMount__Phh4D .ProseMirror p.is-empty:before{color:var(--muted-foreground);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_attachments__KG-fG{display:flex;flex-wrap:wrap;gap:var(--p-2);margin-bottom:var(--p-2)}.ChatPrompt_attachmentItem__QJk7J{flex:1 1 300px;max-width:300px;min-width:0}";
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_fileInput__xdgPn{display:none}.ChatPrompt_attachButton__gi-qF{align-self:flex-end;flex-shrink:0}.ChatPrompt_editorWrap__Q7gat{align-self:stretch;flex:1;min-width:0}.ChatPrompt_editorMount__Phh4D{background:transparent;border:none;border-radius:0!important;box-shadow:none!important;display:flex;flex:1;flex-direction:column;max-height:200px;min-height:40px;min-width:0;padding:0!important}.ChatPrompt_editorMount__Phh4D:focus-within{box-shadow:none!important}.ChatPrompt_editorMount__Phh4D .ProseMirror{border:none!important;box-shadow:none!important;flex:1;margin:0;max-height:200px!important;min-height:40px!important;outline:none!important;overflow-x:hidden!important;overflow-y:auto!important;padding:var(--p-2) 0 0!important;resize:none!important;white-space:pre-wrap;word-break:break-word}.ChatPrompt_editorMount__Phh4D .ProseMirror p.is-empty.is-editor-empty:before{color:var(--muted-foreground);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_attachments__KG-fG{display:flex;flex-wrap:wrap;gap:var(--p-2);margin-bottom:var(--p-2)}.ChatPrompt_attachmentItem__QJk7J{flex:1 1 300px;max-width:300px;min-width:0}";
|
|
4
4
|
var S = {"root":"ChatPrompt_root__5G5bq","composer":"ChatPrompt_composer__H3c3N","fileInput":"ChatPrompt_fileInput__xdgPn","attachButton":"ChatPrompt_attachButton__gi-qF","editorWrap":"ChatPrompt_editorWrap__Q7gat","editorMount":"ChatPrompt_editorMount__Phh4D","submitColumn":"ChatPrompt_submitColumn__0rY1R","attachments":"ChatPrompt_attachments__KG-fG","attachmentItem":"ChatPrompt_attachmentItem__QJk7J"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -5,7 +5,7 @@ import { PaperclipIcon, SendHorizontalIcon } from 'lucide-react';
|
|
|
5
5
|
import { Button } from '../../Button/Button.js';
|
|
6
6
|
import S from './ChatPrompt.styl.js';
|
|
7
7
|
|
|
8
|
-
function ChatPromptComposer({ editor, disabled, trimmedMessage, attachments, attachmentAccept, onAttachmentFiles,
|
|
8
|
+
function ChatPromptComposer({ editor, disabled, trimmedMessage, attachments, attachmentAccept, onAttachmentFiles, }) {
|
|
9
9
|
const fileInputRef = useRef(null);
|
|
10
10
|
const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
|
|
11
11
|
const handleFileInputChange = useCallback((e) => {
|
|
@@ -19,7 +19,7 @@ function ChatPromptComposer({ editor, disabled, trimmedMessage, attachments, att
|
|
|
19
19
|
return (jsxs("div", { className: S.composer, children: [showAttachButton ? (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", accept: attachmentAccept, multiple: true, className: S.fileInput, disabled: disabled, onChange: handleFileInputChange }), jsx(Button, { type: "button", variant: "ghost", icon: true, size: "sm", className: S.attachButton, "aria-label": "Attach file", disabled: disabled, onClick: e => {
|
|
20
20
|
e.preventDefault();
|
|
21
21
|
fileInputRef.current?.click();
|
|
22
|
-
}, children: jsx(PaperclipIcon, { size: 16 }) })] })) : null, jsx("div", { className: S.editorWrap,
|
|
22
|
+
}, children: jsx(PaperclipIcon, { size: 16 }) })] })) : null, jsx("div", { className: S.editorWrap, children: jsx(EditorContent, { editor: editor, className: S.editorMount }) }), jsx("div", { className: S.submitColumn, children: jsx(Button, { type: "submit", size: "sm", disabled: disabled || !canSubmit, onMouseDown: e => e.preventDefault(), children: jsx(SendHorizontalIcon, { size: 16 }) }) })] }));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export { ChatPromptComposer };
|
|
@@ -44,7 +44,6 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
44
44
|
Placeholder.configure({
|
|
45
45
|
placeholder: placeholderText,
|
|
46
46
|
showOnlyWhenEditable: true,
|
|
47
|
-
showOnlyCurrent: false,
|
|
48
47
|
}),
|
|
49
48
|
];
|
|
50
49
|
if (slashItemsStable.length > 0) {
|
|
@@ -70,6 +69,28 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
70
69
|
});
|
|
71
70
|
}, []);
|
|
72
71
|
const trimmedMessage = plainDraft.trim();
|
|
72
|
+
const trimmedMessageRef = useRef(trimmedMessage);
|
|
73
|
+
trimmedMessageRef.current = trimmedMessage;
|
|
74
|
+
const attachmentsCountRef = useRef(attachmentsCount);
|
|
75
|
+
attachmentsCountRef.current = attachmentsCount;
|
|
76
|
+
const onEnterSubmitRef = useRef(onEnterSubmit);
|
|
77
|
+
onEnterSubmitRef.current = onEnterSubmit;
|
|
78
|
+
const handleEditorKeyDown = useCallback((_view, event) => {
|
|
79
|
+
if (!(event.key === 'Enter' &&
|
|
80
|
+
!event.shiftKey &&
|
|
81
|
+
!event.metaKey &&
|
|
82
|
+
!event.ctrlKey)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (slashOpenRef.current)
|
|
86
|
+
return false;
|
|
87
|
+
if (!trimmedMessageRef.current && attachmentsCountRef.current === 0) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
onEnterSubmitRef.current();
|
|
92
|
+
return true;
|
|
93
|
+
}, []);
|
|
73
94
|
const editor = useEditor({
|
|
74
95
|
extensions,
|
|
75
96
|
content: CHAT_PROMPT_EMPTY_DOC,
|
|
@@ -80,6 +101,7 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
80
101
|
spellcheck: 'true',
|
|
81
102
|
'aria-label': ariaLabelComposer,
|
|
82
103
|
},
|
|
104
|
+
handleKeyDown: handleEditorKeyDown,
|
|
83
105
|
},
|
|
84
106
|
onTransaction: ({ editor: ed }) => {
|
|
85
107
|
const dom = chatPromptSafeEditorDom(ed);
|
|
@@ -89,7 +111,7 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
89
111
|
setPlainDraft(ed.getText());
|
|
90
112
|
},
|
|
91
113
|
onCreate: bindEditorDom,
|
|
92
|
-
}, [extensions, bindEditorDom, ariaLabelComposer]);
|
|
114
|
+
}, [extensions, bindEditorDom, ariaLabelComposer, handleEditorKeyDown]);
|
|
93
115
|
useEffect(() => {
|
|
94
116
|
if (!editor)
|
|
95
117
|
return;
|
|
@@ -131,8 +153,6 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
131
153
|
return;
|
|
132
154
|
syncChatPromptComposerHeight(dom, plainDraft);
|
|
133
155
|
}, [editor, plainDraft]);
|
|
134
|
-
const onEnterSubmitRef = useRef(onEnterSubmit);
|
|
135
|
-
onEnterSubmitRef.current = onEnterSubmit;
|
|
136
156
|
const resetAfterSend = useCallback(() => {
|
|
137
157
|
if (!editor)
|
|
138
158
|
return;
|
|
@@ -144,23 +164,10 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
144
164
|
});
|
|
145
165
|
setPlainDraft('');
|
|
146
166
|
}, [editor]);
|
|
147
|
-
const handleComposerKeyDown = useCallback((e) => {
|
|
148
|
-
if (!(e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey))
|
|
149
|
-
return;
|
|
150
|
-
if (!editorDomRef.current?.contains(e.target))
|
|
151
|
-
return;
|
|
152
|
-
if (slashOpenRef.current)
|
|
153
|
-
return;
|
|
154
|
-
if (!trimmedMessage && attachmentsCount === 0)
|
|
155
|
-
return;
|
|
156
|
-
e.preventDefault();
|
|
157
|
-
onEnterSubmitRef.current();
|
|
158
|
-
}, [attachmentsCount, trimmedMessage]);
|
|
159
167
|
return {
|
|
160
168
|
editor,
|
|
161
169
|
trimmedMessage,
|
|
162
170
|
resetAfterSend,
|
|
163
|
-
handleComposerKeyDown,
|
|
164
171
|
};
|
|
165
172
|
}
|
|
166
173
|
|
|
@@ -8,7 +8,7 @@ import S from './PageHeader.styl.js';
|
|
|
8
8
|
|
|
9
9
|
function PageHeader({ breadcrumbs, breadcrumbClientLogo, breadcrumbCompanyName, breadcrumbSidebarTrigger = true, title, subheader, actions, }) {
|
|
10
10
|
const { isScrolled } = useContext(PageContext);
|
|
11
|
-
return (jsx("div", { className: cn(S.root, actions && S.hasActions, isScrolled && S.scrolled), children: jsxs("div", { className: S.inner, children: [jsx(Breadcrumbs, { className: S.breadcrumbs, items: breadcrumbs ?? [], clientLogo: breadcrumbClientLogo, companyName: breadcrumbCompanyName, sidebarTrigger: breadcrumbSidebarTrigger, children: isScrolled && (jsxs("div", { className: S.titleDupe, children: [jsx(BreadCrumbsSeparator, { size: 14 }), title] })) }), jsxs("div", { className: S.main, children: [jsxs("div", { className: S.title, children: [jsx("h1", { children: title }), subheader && (jsx(TextWithDeferTooltip, { className: S.subheader, children: subheader }))] }), actions && jsx("div", { className: S.actions, children: actions })] })] }) }));
|
|
11
|
+
return (jsx("div", { className: cn(S.root, actions && S.hasActions, isScrolled && S.scrolled), children: jsxs("div", { className: S.inner, children: [jsx(Breadcrumbs, { className: S.breadcrumbs, items: breadcrumbs ?? [], clientLogo: breadcrumbClientLogo, companyName: breadcrumbCompanyName, sidebarTrigger: breadcrumbSidebarTrigger, children: isScrolled && (jsxs("div", { className: S.titleDupe, children: [jsx(BreadCrumbsSeparator, { size: 14 }), title] })) }), jsxs("div", { className: S.main, children: [jsxs("div", { className: S.title, children: [jsx("h1", { children: title }), subheader && (jsx(TextWithDeferTooltip, { className: S.subheader, overTrigger: true, children: subheader }))] }), actions && jsx("div", { className: S.actions, children: actions })] })] }) }));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export { PageHeader };
|
|
@@ -6,7 +6,7 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuIte
|
|
|
6
6
|
import { CaretDownIcon } from '@phosphor-icons/react';
|
|
7
7
|
import S from './RegionSelector.styl.js';
|
|
8
8
|
|
|
9
|
-
function RegionSelector({ children, onSelect, className, regions: regionCoordinates, }) {
|
|
9
|
+
function RegionSelector({ children, onSelect, className, regions: regionCoordinates = {}, }) {
|
|
10
10
|
const [open, setOpen] = useState(false);
|
|
11
11
|
const handleRegionSelect = (regionName) => {
|
|
12
12
|
const coords = regionCoordinates[regionName];
|
|
@@ -16,7 +16,7 @@ function RegionSelector({ children, onSelect, className, regions: regionCoordina
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
const regions = Object.keys(regionCoordinates).sort();
|
|
19
|
-
return (jsxs("div", { className: cn(S.root, className), children: [children, jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [jsx(DropdownMenuTrigger, { asChild: true, children: jsx(Button, { variant: "outline", size: "sm", className: S.button, children: jsx(CaretDownIcon, { size: 16 }) }) }), jsx(DropdownMenuContent, { align: "end", children: regions.map(region => (jsx(DropdownMenuItem, { onSelect: () => handleRegionSelect(region), children: region }, region))) })] })] }));
|
|
19
|
+
return (jsxs("div", { className: cn(S.root, className), children: [children, regions.length > 0 && (jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [jsx(DropdownMenuTrigger, { asChild: true, children: jsx(Button, { variant: "outline", size: "sm", className: S.button, children: jsx(CaretDownIcon, { size: 16 }) }) }), jsx(DropdownMenuContent, { align: "end", children: regions.map(region => (jsx(DropdownMenuItem, { onSelect: () => handleRegionSelect(region), children: region }, region))) })] }))] }));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export { RegionSelector };
|
|
@@ -4,19 +4,41 @@ import { Tooltip, TooltipTrigger, TooltipContent } from '../Tooltip/Tooltip.js';
|
|
|
4
4
|
|
|
5
5
|
function TextWithDeferTooltip({ className, children, width, maxWidth, side = 'bottom', overTrigger = false, ...props }) {
|
|
6
6
|
const [withTooltip, setWithTooltip] = useState(false);
|
|
7
|
+
const [tooltipStyles, setTooltipStyles] = useState({});
|
|
7
8
|
const ref = useRef(null);
|
|
9
|
+
const cachedFontSizeRef = useRef(null);
|
|
8
10
|
const handleMouseEnter = () => {
|
|
9
11
|
if (!ref.current)
|
|
10
12
|
return;
|
|
11
13
|
const isOverflowingHorizontally = ref.current.scrollWidth - ref.current.clientWidth > 3;
|
|
12
14
|
const isOverflowingVertically = ref.current.scrollHeight - ref.current.clientHeight > 3;
|
|
13
15
|
if (isOverflowingHorizontally || isOverflowingVertically) {
|
|
16
|
+
const styles = {
|
|
17
|
+
fontSize: cachedFontSizeRef.current || undefined,
|
|
18
|
+
};
|
|
19
|
+
if (ref.current) {
|
|
20
|
+
const { width: rectWidth, left, top, } = ref.current.getBoundingClientRect();
|
|
21
|
+
styles.width = `${width ?? rectWidth}px`;
|
|
22
|
+
if (!cachedFontSizeRef.current) {
|
|
23
|
+
const { fontSize } = window.getComputedStyle(ref.current);
|
|
24
|
+
cachedFontSizeRef.current = fontSize;
|
|
25
|
+
styles.fontSize = fontSize;
|
|
26
|
+
}
|
|
27
|
+
if (overTrigger) {
|
|
28
|
+
styles.transform = `translate(${left}px, ${top}px) !important`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
setTooltipStyles(styles);
|
|
14
32
|
setWithTooltip(true);
|
|
15
33
|
}
|
|
16
34
|
};
|
|
17
35
|
const textElement = (jsx("div", { ref: ref, className: className, onMouseEnter: handleMouseEnter, ...props, children: children }));
|
|
18
36
|
if (withTooltip) {
|
|
19
|
-
|
|
37
|
+
const tooltipSide = overTrigger ? 'bottom' : side;
|
|
38
|
+
return (jsxs(Tooltip, { open: withTooltip, onOpenChange: setWithTooltip, children: [jsx(TooltipTrigger, { asChild: true, children: textElement }), jsx(TooltipContent, { side: tooltipSide, style: {
|
|
39
|
+
...(maxWidth !== undefined && { maxWidth: `${maxWidth}px` }),
|
|
40
|
+
...tooltipStyles,
|
|
41
|
+
}, overTrigger: overTrigger, children: children })] }));
|
|
20
42
|
}
|
|
21
43
|
return textElement;
|
|
22
44
|
}
|
|
@@ -83,7 +83,7 @@ function DriverCard({ selectedDriver, isLoading, inQueue = false, driverSelector
|
|
|
83
83
|
const directionText = direction > 0 ? 'Positive' : 'Negative';
|
|
84
84
|
const DirectionIcon = direction > 0 ? TrendUpIcon : TrendDownIcon;
|
|
85
85
|
const nameElem = (jsx("h4", { className: `${S.driverTitle} ${S.truncated}`, children: name }));
|
|
86
|
-
return (jsx(Card, { className: S.root, paddingSize: "l", children: jsx(CardContent, { noScroll: true, children: jsxs("div", { className: S.cardContent, children: [jsx("div", { className: S.driverHeader, children: jsxs("div", { className: S.headerContent, children: [jsxs("div", { className: S.topHeader, children: [jsxs("p", { className: S.categoryInfo, children: [jsx("span", { className: S.categoryIcon, children: getCategoryIcon(category) }), jsx("span", { className: S.categoryText, children: category })] }), driverSelector] }), name.length > 60 ? (jsxs(Tooltip, { children: [jsx(LabelWithId, { id: id, label: jsx(TooltipTrigger, { asChild: true, children: nameElem }) }), jsx(TooltipContent, { side: "left", className: S.tooltipContent, children: jsx("div", { className: S.tooltipTitle, children: name }) })] })) : (jsx(LabelWithId, { id: id, label: nameElem })), jsx("p", { className: S.regionDisplay, children: regionDisplay })] }) }), jsx("div", { className: S.metricsSection, children: jsx("div", { className: S.importanceScore, children: importanceDisplay }) }), jsxs("div", { className: S.directionLagSection, children: [jsxs(Badge, { variant: direction > 0 ? 'green' : 'red', className: S.directionBadge, children: [jsx(DirectionIcon, { className: S.trendIcon }), directionText, " correlation"] }), jsxs("span", { className: S.lagInfo, children: ["Lag: ", lag] })] }), jsx(DriverPerformanceChart, { driver: selectedDriver }), jsx("p", { className: S.description, children: summary ?? '' })] }) }) }));
|
|
86
|
+
return (jsx(Card, { className: S.root, paddingSize: "l", children: jsx(CardContent, { noScroll: true, children: jsxs("div", { className: S.cardContent, children: [jsx("div", { className: S.driverHeader, children: jsxs("div", { className: S.headerContent, children: [jsxs("div", { className: S.topHeader, children: [jsxs("p", { className: S.categoryInfo, children: [jsx("span", { className: S.categoryIcon, children: getCategoryIcon(category) }), jsx("span", { className: S.categoryText, children: category })] }), driverSelector] }), name.length > 60 ? (jsxs(Tooltip, { children: [jsx(LabelWithId, { id: id, label: jsx(TooltipTrigger, { asChild: true, children: nameElem }) }), jsx(TooltipContent, { side: "left", className: S.tooltipContent, overTrigger: true, children: jsx("div", { className: S.tooltipTitle, children: name }) })] })) : (jsx(LabelWithId, { id: id, label: nameElem })), jsx("p", { className: S.regionDisplay, children: regionDisplay })] }) }), jsx("div", { className: S.metricsSection, children: jsx("div", { className: S.importanceScore, children: importanceDisplay }) }), jsxs("div", { className: S.directionLagSection, children: [jsxs(Badge, { variant: direction > 0 ? 'green' : 'red', className: S.directionBadge, children: [jsx(DirectionIcon, { className: S.trendIcon }), directionText, " correlation"] }), jsxs("span", { className: S.lagInfo, children: ["Lag: ", lag] })] }), jsx(DriverPerformanceChart, { driver: selectedDriver }), jsx("p", { className: S.description, children: summary ?? '' })] }) }) }));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export { DriverCard };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
1
|
import type { Editor } from '@tiptap/core';
|
|
3
2
|
import type { ChatAttachmentDropItem } from '../Chat.types';
|
|
4
3
|
export type ChatPromptComposerProps = {
|
|
@@ -8,6 +7,5 @@ export type ChatPromptComposerProps = {
|
|
|
8
7
|
attachments: ChatAttachmentDropItem[];
|
|
9
8
|
attachmentAccept?: string;
|
|
10
9
|
onAttachmentFiles?: (files: File[]) => void;
|
|
11
|
-
onComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
12
10
|
};
|
|
13
|
-
export declare function ChatPromptComposer({ editor, disabled, trimmedMessage, attachments, attachmentAccept, onAttachmentFiles,
|
|
11
|
+
export declare function ChatPromptComposer({ editor, disabled, trimmedMessage, attachments, attachmentAccept, onAttachmentFiles, }: ChatPromptComposerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
1
|
import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
|
|
3
2
|
import type { Editor } from '@tiptap/core';
|
|
4
3
|
export type UseChatPromptEditorOptions = {
|
|
@@ -16,6 +15,5 @@ export type UseChatPromptEditorResult = {
|
|
|
16
15
|
editor: Editor | null;
|
|
17
16
|
trimmedMessage: string;
|
|
18
17
|
resetAfterSend: () => void;
|
|
19
|
-
handleComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
20
18
|
};
|
|
21
19
|
export declare function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlashItemCommand, prefillMessage, attachmentsCount, onEnterSubmit, }: UseChatPromptEditorOptions): UseChatPromptEditorResult;
|
|
@@ -6,7 +6,7 @@ export interface RegionSelectorProps {
|
|
|
6
6
|
}) => void;
|
|
7
7
|
className?: string;
|
|
8
8
|
/** Region name → coordinates (app-defined; replaces app-only drivers catalog). */
|
|
9
|
-
regions
|
|
9
|
+
regions?: Record<string, {
|
|
10
10
|
longitude: number;
|
|
11
11
|
latitude: number;
|
|
12
12
|
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ChatSheetPage(): import("react/jsx-runtime").JSX.Element;
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ChartAreaInteractive
|
|
2
|
+
|
|
3
|
+
Renders: time-series chart with historical line, forecast lines, optional pin / quantile-band / threshold overlays.
|
|
4
|
+
|
|
5
|
+
Use when: custom forecast UI with overlays or full chart control.
|
|
6
|
+
Not when: packaged performance or drivers-comparison views (use PerformanceChart or DriversComparisonChart).
|
|
7
|
+
|
|
8
|
+
Host provides:
|
|
9
|
+
|
|
10
|
+
- `chartData`, `forecastData` built from API
|
|
11
|
+
- `timeRange` / `onTimeRangeChange` or brush-only range
|
|
12
|
+
- Optional `mode`: pin | intervals | thresholds + overlay state
|
|
13
|
+
- Analysis selector and fetch outside widget
|
|
14
|
+
|
|
15
|
+
Report tile: `dataset_card` — host loads dataset + analysis; chart inside dashboard card.
|
|
16
|
+
|
|
17
|
+
Requires: `chartData`; `forecastData`; `loading`; `toggleLegendSeries` / `ensureAnalysisSeriesVisible` when legend is external.
|
|
18
|
+
|
|
19
|
+
Empty/loading: `loading`, `error`; empty data shows chart empty state via host message props.
|
|
@@ -62,7 +62,7 @@ INPUT_MAX_HEIGHT = 200px
|
|
|
62
62
|
overflow-y auto !important
|
|
63
63
|
overflow-x hidden !important
|
|
64
64
|
|
|
65
|
-
& :global(.ProseMirror p.is-empty::before)
|
|
65
|
+
& :global(.ProseMirror p.is-empty.is-editor-empty::before)
|
|
66
66
|
color var(--muted-foreground)
|
|
67
67
|
content attr(data-placeholder)
|
|
68
68
|
float left
|
|
@@ -24,16 +24,15 @@ export function ChatPrompt({
|
|
|
24
24
|
const attachmentsCount = attachments.length;
|
|
25
25
|
|
|
26
26
|
const emitSubmitRef = useRef(() => {});
|
|
27
|
-
const { editor, trimmedMessage, resetAfterSend
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
27
|
+
const { editor, trimmedMessage, resetAfterSend } = useChatPromptEditor({
|
|
28
|
+
disabled,
|
|
29
|
+
placeholder,
|
|
30
|
+
slashCommandItems,
|
|
31
|
+
onSlashItemCommand,
|
|
32
|
+
prefillMessage,
|
|
33
|
+
attachmentsCount,
|
|
34
|
+
onEnterSubmit: () => emitSubmitRef.current(),
|
|
35
|
+
});
|
|
37
36
|
|
|
38
37
|
const emitSubmitAndClear = useCallback(() => {
|
|
39
38
|
if (!editor) return;
|
|
@@ -79,7 +78,6 @@ export function ChatPrompt({
|
|
|
79
78
|
attachments={attachments}
|
|
80
79
|
attachmentAccept={attachmentAccept}
|
|
81
80
|
onAttachmentFiles={onAttachmentFiles}
|
|
82
|
-
onComposerKeyDown={handleComposerKeyDown}
|
|
83
81
|
/>
|
|
84
82
|
{footer}
|
|
85
83
|
</form>
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ChangeEvent,
|
|
3
|
-
type KeyboardEvent as ReactKeyboardEvent,
|
|
4
|
-
useCallback,
|
|
5
|
-
useRef,
|
|
6
|
-
} from 'react';
|
|
1
|
+
import { type ChangeEvent, useCallback, useRef } from 'react';
|
|
7
2
|
|
|
8
3
|
import type { Editor } from '@tiptap/core';
|
|
9
4
|
import { EditorContent } from '@tiptap/react';
|
|
@@ -20,7 +15,6 @@ export type ChatPromptComposerProps = {
|
|
|
20
15
|
attachments: ChatAttachmentDropItem[];
|
|
21
16
|
attachmentAccept?: string;
|
|
22
17
|
onAttachmentFiles?: (files: File[]) => void;
|
|
23
|
-
onComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
24
18
|
};
|
|
25
19
|
|
|
26
20
|
export function ChatPromptComposer({
|
|
@@ -30,7 +24,6 @@ export function ChatPromptComposer({
|
|
|
30
24
|
attachments,
|
|
31
25
|
attachmentAccept,
|
|
32
26
|
onAttachmentFiles,
|
|
33
|
-
onComposerKeyDown,
|
|
34
27
|
}: ChatPromptComposerProps) {
|
|
35
28
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
36
29
|
const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
|
|
@@ -79,7 +72,7 @@ export function ChatPromptComposer({
|
|
|
79
72
|
</>
|
|
80
73
|
) : null}
|
|
81
74
|
|
|
82
|
-
<div className={S.editorWrap}
|
|
75
|
+
<div className={S.editorWrap}>
|
|
83
76
|
<EditorContent editor={editor} className={S.editorMount} />
|
|
84
77
|
</div>
|
|
85
78
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type KeyboardEvent as ReactKeyboardEvent,
|
|
3
2
|
useCallback,
|
|
4
3
|
useEffect,
|
|
5
4
|
useLayoutEffect,
|
|
@@ -43,7 +42,6 @@ export type UseChatPromptEditorResult = {
|
|
|
43
42
|
editor: Editor | null;
|
|
44
43
|
trimmedMessage: string;
|
|
45
44
|
resetAfterSend: () => void;
|
|
46
|
-
handleComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
47
45
|
};
|
|
48
46
|
|
|
49
47
|
export function useChatPromptEditor({
|
|
@@ -94,7 +92,6 @@ export function useChatPromptEditor({
|
|
|
94
92
|
Placeholder.configure({
|
|
95
93
|
placeholder: placeholderText,
|
|
96
94
|
showOnlyWhenEditable: true,
|
|
97
|
-
showOnlyCurrent: false,
|
|
98
95
|
}),
|
|
99
96
|
];
|
|
100
97
|
if (slashItemsStable.length > 0) {
|
|
@@ -126,6 +123,38 @@ export function useChatPromptEditor({
|
|
|
126
123
|
|
|
127
124
|
const trimmedMessage = plainDraft.trim();
|
|
128
125
|
|
|
126
|
+
const trimmedMessageRef = useRef(trimmedMessage);
|
|
127
|
+
trimmedMessageRef.current = trimmedMessage;
|
|
128
|
+
|
|
129
|
+
const attachmentsCountRef = useRef(attachmentsCount);
|
|
130
|
+
attachmentsCountRef.current = attachmentsCount;
|
|
131
|
+
|
|
132
|
+
const onEnterSubmitRef = useRef(onEnterSubmit);
|
|
133
|
+
onEnterSubmitRef.current = onEnterSubmit;
|
|
134
|
+
|
|
135
|
+
const handleEditorKeyDown = useCallback(
|
|
136
|
+
(_view: unknown, event: KeyboardEvent) => {
|
|
137
|
+
if (
|
|
138
|
+
!(
|
|
139
|
+
event.key === 'Enter' &&
|
|
140
|
+
!event.shiftKey &&
|
|
141
|
+
!event.metaKey &&
|
|
142
|
+
!event.ctrlKey
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (slashOpenRef.current) return false;
|
|
148
|
+
if (!trimmedMessageRef.current && attachmentsCountRef.current === 0) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
onEnterSubmitRef.current();
|
|
153
|
+
return true;
|
|
154
|
+
},
|
|
155
|
+
[],
|
|
156
|
+
);
|
|
157
|
+
|
|
129
158
|
const editor = useEditor(
|
|
130
159
|
{
|
|
131
160
|
extensions,
|
|
@@ -137,6 +166,7 @@ export function useChatPromptEditor({
|
|
|
137
166
|
spellcheck: 'true',
|
|
138
167
|
'aria-label': ariaLabelComposer,
|
|
139
168
|
},
|
|
169
|
+
handleKeyDown: handleEditorKeyDown,
|
|
140
170
|
},
|
|
141
171
|
onTransaction: ({ editor: ed }) => {
|
|
142
172
|
const dom = chatPromptSafeEditorDom(ed);
|
|
@@ -147,7 +177,7 @@ export function useChatPromptEditor({
|
|
|
147
177
|
},
|
|
148
178
|
onCreate: bindEditorDom,
|
|
149
179
|
},
|
|
150
|
-
[extensions, bindEditorDom, ariaLabelComposer],
|
|
180
|
+
[extensions, bindEditorDom, ariaLabelComposer, handleEditorKeyDown],
|
|
151
181
|
);
|
|
152
182
|
|
|
153
183
|
useEffect(() => {
|
|
@@ -188,9 +218,6 @@ export function useChatPromptEditor({
|
|
|
188
218
|
syncChatPromptComposerHeight(dom, plainDraft);
|
|
189
219
|
}, [editor, plainDraft]);
|
|
190
220
|
|
|
191
|
-
const onEnterSubmitRef = useRef(onEnterSubmit);
|
|
192
|
-
onEnterSubmitRef.current = onEnterSubmit;
|
|
193
|
-
|
|
194
221
|
const resetAfterSend = useCallback(() => {
|
|
195
222
|
if (!editor) return;
|
|
196
223
|
editor.chain().clearContent().focus().run();
|
|
@@ -201,23 +228,9 @@ export function useChatPromptEditor({
|
|
|
201
228
|
setPlainDraft('');
|
|
202
229
|
}, [editor]);
|
|
203
230
|
|
|
204
|
-
const handleComposerKeyDown = useCallback(
|
|
205
|
-
(e: ReactKeyboardEvent) => {
|
|
206
|
-
if (!(e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey))
|
|
207
|
-
return;
|
|
208
|
-
if (!editorDomRef.current?.contains(e.target as Node)) return;
|
|
209
|
-
if (slashOpenRef.current) return;
|
|
210
|
-
if (!trimmedMessage && attachmentsCount === 0) return;
|
|
211
|
-
e.preventDefault();
|
|
212
|
-
onEnterSubmitRef.current();
|
|
213
|
-
},
|
|
214
|
-
[attachmentsCount, trimmedMessage],
|
|
215
|
-
);
|
|
216
|
-
|
|
217
231
|
return {
|
|
218
232
|
editor,
|
|
219
233
|
trimmedMessage,
|
|
220
234
|
resetAfterSend,
|
|
221
|
-
handleComposerKeyDown,
|
|
222
235
|
};
|
|
223
236
|
}
|
|
@@ -17,7 +17,7 @@ export function RegionSelector({
|
|
|
17
17
|
children,
|
|
18
18
|
onSelect,
|
|
19
19
|
className,
|
|
20
|
-
regions: regionCoordinates,
|
|
20
|
+
regions: regionCoordinates = {},
|
|
21
21
|
}: RegionSelectorProps) {
|
|
22
22
|
const [open, setOpen] = useState(false);
|
|
23
23
|
|
|
@@ -34,23 +34,25 @@ export function RegionSelector({
|
|
|
34
34
|
return (
|
|
35
35
|
<div className={cn(S.root, className)}>
|
|
36
36
|
{children}
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
{regions.length > 0 && (
|
|
38
|
+
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
39
|
+
<DropdownMenuTrigger asChild>
|
|
40
|
+
<Button variant="outline" size="sm" className={S.button}>
|
|
41
|
+
<CaretDownIcon size={16} />
|
|
42
|
+
</Button>
|
|
43
|
+
</DropdownMenuTrigger>
|
|
44
|
+
<DropdownMenuContent align="end">
|
|
45
|
+
{regions.map(region => (
|
|
46
|
+
<DropdownMenuItem
|
|
47
|
+
key={region}
|
|
48
|
+
onSelect={() => handleRegionSelect(region)}
|
|
49
|
+
>
|
|
50
|
+
{region}
|
|
51
|
+
</DropdownMenuItem>
|
|
52
|
+
))}
|
|
53
|
+
</DropdownMenuContent>
|
|
54
|
+
</DropdownMenu>
|
|
55
|
+
)}
|
|
54
56
|
</div>
|
|
55
57
|
);
|
|
56
58
|
}
|
|
@@ -6,5 +6,5 @@ export interface RegionSelectorProps {
|
|
|
6
6
|
) => void;
|
|
7
7
|
className?: string;
|
|
8
8
|
/** Region name → coordinates (app-defined; replaces app-only drivers catalog). */
|
|
9
|
-
regions
|
|
9
|
+
regions?: Record<string, { longitude: number; latitude: number }>;
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef, useState } from 'react';
|
|
1
|
+
import { CSSProperties, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Tooltip,
|
|
@@ -18,7 +18,9 @@ function TextWithDeferTooltip({
|
|
|
18
18
|
...props
|
|
19
19
|
}: TextWithDeferTooltipProps) {
|
|
20
20
|
const [withTooltip, setWithTooltip] = useState(false);
|
|
21
|
+
const [tooltipStyles, setTooltipStyles] = useState<CSSProperties>({});
|
|
21
22
|
const ref = useRef<HTMLDivElement>(null);
|
|
23
|
+
const cachedFontSizeRef = useRef<string | null>(null);
|
|
22
24
|
|
|
23
25
|
const handleMouseEnter = () => {
|
|
24
26
|
if (!ref.current) return;
|
|
@@ -29,6 +31,31 @@ function TextWithDeferTooltip({
|
|
|
29
31
|
ref.current.scrollHeight - ref.current.clientHeight > 3;
|
|
30
32
|
|
|
31
33
|
if (isOverflowingHorizontally || isOverflowingVertically) {
|
|
34
|
+
const styles: CSSProperties = {
|
|
35
|
+
fontSize: cachedFontSizeRef.current || undefined,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (ref.current) {
|
|
39
|
+
const {
|
|
40
|
+
width: rectWidth,
|
|
41
|
+
left,
|
|
42
|
+
top,
|
|
43
|
+
} = ref.current.getBoundingClientRect();
|
|
44
|
+
|
|
45
|
+
styles.width = `${width ?? rectWidth}px`;
|
|
46
|
+
|
|
47
|
+
if (!cachedFontSizeRef.current) {
|
|
48
|
+
const { fontSize } = window.getComputedStyle(ref.current);
|
|
49
|
+
cachedFontSizeRef.current = fontSize;
|
|
50
|
+
styles.fontSize = fontSize;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (overTrigger) {
|
|
54
|
+
styles.transform = `translate(${left}px, ${top}px) !important`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setTooltipStyles(styles);
|
|
32
59
|
setWithTooltip(true);
|
|
33
60
|
}
|
|
34
61
|
};
|
|
@@ -45,14 +72,18 @@ function TextWithDeferTooltip({
|
|
|
45
72
|
);
|
|
46
73
|
|
|
47
74
|
if (withTooltip) {
|
|
75
|
+
const tooltipSide = overTrigger ? 'bottom' : side;
|
|
76
|
+
|
|
48
77
|
return (
|
|
49
78
|
<Tooltip open={withTooltip} onOpenChange={setWithTooltip}>
|
|
50
79
|
<TooltipTrigger asChild>{textElement}</TooltipTrigger>
|
|
51
80
|
<TooltipContent
|
|
52
|
-
side={
|
|
81
|
+
side={tooltipSide}
|
|
82
|
+
style={{
|
|
83
|
+
...(maxWidth !== undefined && { maxWidth: `${maxWidth}px` }),
|
|
84
|
+
...tooltipStyles,
|
|
85
|
+
}}
|
|
53
86
|
overTrigger={overTrigger}
|
|
54
|
-
maxWidth={maxWidth}
|
|
55
|
-
style={width !== undefined ? { width: `${width}px` } : undefined}
|
|
56
87
|
>
|
|
57
88
|
{children}
|
|
58
89
|
</TooltipContent>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!-- not a component — authoring guide only -->
|
|
2
|
+
|
|
3
|
+
# Widget `AGENT.md` authoring
|
|
4
|
+
|
|
5
|
+
Each component file ≤18 lines. Signal for LLM prompts — no noise.
|
|
6
|
+
|
|
7
|
+
**Include:** Renders (1 sentence); Use when / Not when; Host provides (3–5 bullets); Report tile (one line or "Not used"); Requires (prop names + role); Empty/loading (one line).
|
|
8
|
+
|
|
9
|
+
**Do not include:** import examples; type/doc/demo/glossary links; page-shell boilerplate; related-components lists unless choosing between exports; implementation/styling/keyboard notes unless binding-relevant; secrets, env vars, API URLs.
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# Name
|
|
13
|
+
|
|
14
|
+
Renders: …
|
|
15
|
+
Use when: …
|
|
16
|
+
Not when: …
|
|
17
|
+
Host provides: …
|
|
18
|
+
Report tile: …
|
|
19
|
+
Requires: …
|
|
20
|
+
Empty/loading: …
|
|
21
|
+
```
|
|
@@ -177,7 +177,11 @@ export function DriverCard({
|
|
|
177
177
|
id={id}
|
|
178
178
|
label={<TooltipTrigger asChild>{nameElem}</TooltipTrigger>}
|
|
179
179
|
/>
|
|
180
|
-
<TooltipContent
|
|
180
|
+
<TooltipContent
|
|
181
|
+
side="left"
|
|
182
|
+
className={S.tooltipContent}
|
|
183
|
+
overTrigger
|
|
184
|
+
>
|
|
181
185
|
<div className={S.tooltipTitle}>{name}</div>
|
|
182
186
|
</TooltipContent>
|
|
183
187
|
</Tooltip>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# DriverMap
|
|
2
|
+
|
|
3
|
+
Renders: world map with regional driver badges, bottom strip for world-level drivers, selection highlight.
|
|
4
|
+
|
|
5
|
+
Use when: geographic driver exploration for one analysis.
|
|
6
|
+
Not when: driver detail metrics card alone (pair with DriverCard) or normalized series chart (use DriversComparisonChart).
|
|
7
|
+
|
|
8
|
+
Host provides:
|
|
9
|
+
|
|
10
|
+
- `drivers` as `DriverData[]` from analysis API
|
|
11
|
+
- Controlled `selectedDriver` + `setSelectedDriver`
|
|
12
|
+
- `isLoading` while fetching drivers
|
|
13
|
+
|
|
14
|
+
Report tile: `drivers_map` — tile resolves analysis id, fetches drivers, passes list + selection (see EmbeddedAnalysisSelector pattern).
|
|
15
|
+
|
|
16
|
+
Requires: `drivers`; `isLoading`; `selectedDriver`; `setSelectedDriver`.
|
|
17
|
+
|
|
18
|
+
Empty/loading: `isLoading` shows overlay; empty `drivers` leaves map without badges.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# DriversComparisonChart
|
|
2
|
+
|
|
3
|
+
Renders: target vs drivers backtests chart with ChartAreaInteractive plus table; row click toggles series visibility.
|
|
4
|
+
|
|
5
|
+
Use when: drivers comparison tab with normalized target and driver series from backtests payload.
|
|
6
|
+
Not when: geographic map or performance horizons — use DriverMap or PerformanceChart.
|
|
7
|
+
|
|
8
|
+
Host provides:
|
|
9
|
+
|
|
10
|
+
- `payload`: BacktestsComponentPayload from platform SDK (host fetch per analysis)
|
|
11
|
+
- Optional `datasetHistorical` overlay
|
|
12
|
+
- `seriesInitKey` when selected analysis changes
|
|
13
|
+
|
|
14
|
+
Report tile: Not used in report tiles.
|
|
15
|
+
|
|
16
|
+
Requires: `payload` — target + driver normalized_series; `loading` / `chartLoading` — spinners; `seriesInitKey` — reset visible series on analysis change; `runAnalysisHint` / `statusHint` — empty/error text.
|
|
17
|
+
|
|
18
|
+
Empty/loading: loading props shimmer chart; null `payload` with `runAnalysisHint` prompts to run analysis.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# PerformanceChart
|
|
2
|
+
|
|
3
|
+
Renders: forecast performance on ChartAreaInteractive — per-horizon tab (24m window, MAE/MAPE table) and spaghetti tab (all horizons overlaid).
|
|
4
|
+
|
|
5
|
+
Use when: dataset performance tab with horizon selector and error metrics.
|
|
6
|
+
Not when: simple forecast card or driver backtests — use ChartAreaInteractive or DriversComparisonChart.
|
|
7
|
+
|
|
8
|
+
Host provides:
|
|
9
|
+
|
|
10
|
+
- `performanceData` (PerformanceChartPayload) and `historicalData` from performance API
|
|
11
|
+
- Analysis selection and fetch outside widget
|
|
12
|
+
- Optional `forecastData`, `customPerformanceMatrix`, `userSeries` for spaghetti
|
|
13
|
+
|
|
14
|
+
Report tile: Not used in report tiles.
|
|
15
|
+
|
|
16
|
+
Requires: `performanceData` — model/drift forecasts and metrics; `historicalData` — baseline series; `loading` / `chartLoading` / `performanceDataLoading` — spinners; `runAnalysisHint` / `statusHint` — empty states.
|
|
17
|
+
|
|
18
|
+
Empty/loading: loading props show shimmer/spinner; null `performanceData` with `runAnalysisHint` prompts to run analysis.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# SidebarDatasetsItemsGrouped
|
|
2
|
+
|
|
3
|
+
Renders: expandable sidebar groups of datasets (by target type, regions, or categories).
|
|
4
|
+
|
|
5
|
+
Use when: app shell needs grouped dataset navigation.
|
|
6
|
+
Not when: in-page or report content (no sidebar slot).
|
|
7
|
+
|
|
8
|
+
Host provides:
|
|
9
|
+
|
|
10
|
+
- `datasets` list (`SidebarDatasetsItemsGroupedDataset`)
|
|
11
|
+
- `groupBy`, `selectedDatasetId`, `onDatasetClick`
|
|
12
|
+
- Optional `preItems` / `postItems`, `defaultExpandedGroupNames`
|
|
13
|
+
|
|
14
|
+
Report tile: Not used in report tiles.
|
|
15
|
+
|
|
16
|
+
Requires: `datasets`; `groupBy`; `onDatasetClick` for navigation.
|
|
17
|
+
|
|
18
|
+
Empty/loading: empty `datasets` renders no groups; loading handled by host before pass-in.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { SlashCommandItem } from '#uilib/components/ui/Chat';
|
|
2
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
3
|
+
import { MessageSquare } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
6
|
+
import { DOCS_CHAT_USER_KEY } from '../docsConstants';
|
|
7
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
8
|
+
|
|
9
|
+
const DOCS_CHAT_SHEET_SCOPE_ID = `${DOCS_CHAT_USER_KEY}-docs-chat-sheet-portal`;
|
|
10
|
+
|
|
11
|
+
const DOCS_SAMPLE_SLASH_ITEMS: SlashCommandItem[] = [
|
|
12
|
+
{
|
|
13
|
+
id: 'sample-command',
|
|
14
|
+
label: 'sample-command',
|
|
15
|
+
description: 'Demo slash item for portal ChatSheet regression.',
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export default function ChatSheetPage() {
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<AppPageHeader
|
|
23
|
+
breadcrumbs={[{ label: 'Chat' }, { label: 'Chat sheet' }]}
|
|
24
|
+
title="Chat sheet (portal)"
|
|
25
|
+
subheader="Same integration as design-demo: ChatSheet in page header actions, chat panel portaled into the shell sidebar slot. No inline ChatChrome on this page."
|
|
26
|
+
actions={
|
|
27
|
+
<DocsHeaderActions
|
|
28
|
+
scopeId={DOCS_CHAT_SHEET_SCOPE_ID}
|
|
29
|
+
slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS}
|
|
30
|
+
triggerLabel={
|
|
31
|
+
<>
|
|
32
|
+
<MessageSquare size={20} />
|
|
33
|
+
AI Assistant
|
|
34
|
+
</>
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
}
|
|
38
|
+
/>
|
|
39
|
+
<PageContentSection>
|
|
40
|
+
<p style={{ marginBottom: 16, fontSize: 14, lineHeight: 1.5 }}>
|
|
41
|
+
Open <strong>AI Assistant</strong> in the header. The composer runs
|
|
42
|
+
inside the portaled chat panel (not inline on this page).
|
|
43
|
+
</p>
|
|
44
|
+
<h3 style={{ marginBottom: 8, fontSize: 14, fontWeight: 600 }}>
|
|
45
|
+
Regression checklist
|
|
46
|
+
</h3>
|
|
47
|
+
<ul
|
|
48
|
+
style={{ margin: 0, paddingLeft: 20, fontSize: 14, lineHeight: 1.6 }}
|
|
49
|
+
>
|
|
50
|
+
<li>Plain Enter with text → submit and clear composer</li>
|
|
51
|
+
<li>
|
|
52
|
+
Plain Enter must not create empty lines with duplicated placeholders
|
|
53
|
+
</li>
|
|
54
|
+
<li>Shift+Enter with text → multiline, no placeholder after text</li>
|
|
55
|
+
<li>Empty Enter → no submit</li>
|
|
56
|
+
<li>Submit button and slash menu still work</li>
|
|
57
|
+
</ul>
|
|
58
|
+
</PageContentSection>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -11,9 +11,6 @@ import { DocsHeaderActions } from '../docsHeaderActions';
|
|
|
11
11
|
|
|
12
12
|
const TOOLTIP_SIDES = ['left', 'top', 'bottom', 'right'] as const;
|
|
13
13
|
|
|
14
|
-
const OVER_TRIGGER_TEXT =
|
|
15
|
-
'Actual Order volume for Baerlocher MB 301, a compound that includes stearin as a component. MB 301 is typically used in construction-grade applications.';
|
|
16
|
-
|
|
17
14
|
export default function TooltipPage() {
|
|
18
15
|
return (
|
|
19
16
|
<>
|
|
@@ -37,34 +34,6 @@ export default function TooltipPage() {
|
|
|
37
34
|
))}
|
|
38
35
|
</div>
|
|
39
36
|
</PageContentSection>
|
|
40
|
-
<PageContentSection>
|
|
41
|
-
<p style={{ margin: '0 0 8px', fontWeight: 600 }}>Over trigger</p>
|
|
42
|
-
<div
|
|
43
|
-
style={{
|
|
44
|
-
maxWidth: 500,
|
|
45
|
-
border: '1px dashed var(--border)',
|
|
46
|
-
padding: 8,
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
<Tooltip>
|
|
50
|
-
<TooltipTrigger asChild>
|
|
51
|
-
<div
|
|
52
|
-
style={{
|
|
53
|
-
overflow: 'hidden',
|
|
54
|
-
textOverflow: 'ellipsis',
|
|
55
|
-
whiteSpace: 'nowrap',
|
|
56
|
-
fontWeight: 600,
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
{OVER_TRIGGER_TEXT}
|
|
60
|
-
</div>
|
|
61
|
-
</TooltipTrigger>
|
|
62
|
-
<TooltipContent overTrigger maxWidth={400}>
|
|
63
|
-
{OVER_TRIGGER_TEXT}
|
|
64
|
-
</TooltipContent>
|
|
65
|
-
</Tooltip>
|
|
66
|
-
</div>
|
|
67
|
-
</PageContentSection>
|
|
68
37
|
</>
|
|
69
38
|
);
|
|
70
39
|
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -115,6 +115,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
115
115
|
section: 'Chat',
|
|
116
116
|
load: () => import('./pages/ChatSlashCommandsPage'),
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
slug: 'chat-sheet',
|
|
120
|
+
title: 'Chat sheet (portal)',
|
|
121
|
+
section: 'Chat',
|
|
122
|
+
load: () => import('./pages/ChatSheetPage'),
|
|
123
|
+
},
|
|
118
124
|
{
|
|
119
125
|
slug: 'chat-user-csv-attachment',
|
|
120
126
|
title: 'Chat user CSV attachment',
|