@sybilion/uilib 1.3.53 → 1.3.54

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.
@@ -24,12 +24,12 @@ function CardDescription({ className, ...props }) {
24
24
  function CardAction({ className, ...props }) {
25
25
  return (jsx("div", { "data-slot": "card-action", className: cn(S.action, className), ...props }));
26
26
  }
27
- function CardContent({ className, centered, fullHeight, noScroll, children, autoScrollBottom, ...props }) {
28
- const contentRef = useRef(null);
27
+ function CardContent({ className, centered, fullHeight, noScroll, children, autoScrollBottom, yScrollbarClassName, ...props }) {
28
+ const scrollInnerRef = useRef(null);
29
29
  const contentDiv = (jsx("div", { "data-slot": "card-content", className: cn(className, centered && S.centered, fullHeight && S.fullHeight), ...props, children: children }));
30
30
  useEffect(() => {
31
31
  if (!noScroll && autoScrollBottom) {
32
- const inner = contentRef.current?.firstChild;
32
+ const inner = scrollInnerRef.current;
33
33
  if (!inner)
34
34
  return;
35
35
  if (inner.scrollHeight > SCROLL_OFFSET) {
@@ -46,9 +46,9 @@ function CardContent({ className, centered, fullHeight, noScroll, children, auto
46
46
  }, [noScroll, autoScrollBottom]);
47
47
  if (noScroll)
48
48
  return contentDiv;
49
- return (
50
- // @ts-ignore
51
- jsx(Scroll, { y: true, autoHide: true, fadeSize: "m", className: S.scroll, ref: contentRef, children: contentDiv }));
49
+ return (jsx(Scroll, { y: true, autoHide: true, fadeSize: "m", className: S.scroll, yScrollbarClassName: yScrollbarClassName, onInnerRef: el => {
50
+ scrollInnerRef.current = el;
51
+ }, children: contentDiv }));
52
52
  }
53
53
  function CardFooter({ className, ...props }) {
54
54
  return (jsx("div", { "data-slot": "card-footer", className: cn(S.footer, className), ...props }));
@@ -7,7 +7,7 @@ import { APP_MODAL_ID } from '../../../constants/appMount.js';
7
7
  import { XIcon } from '@phosphor-icons/react';
8
8
  import S from './Dialog.styl.js';
9
9
 
10
- function Dialog({ open, onOpenChange, disabled, trigger, title, subtitle, disableCloseButton, icon, content, contentClassName, footer, footerClassName, footerAlignment = 'center', size = 'default', className, noScroll, maxHeight, autoScrollBottom, width, }) {
10
+ function Dialog({ open, onOpenChange, disabled, trigger, title, subtitle, disableCloseButton, icon, content, contentClassName, contentScrollbarClassName, footer, footerClassName, footerAlignment = 'center', size = 'default', className, noScroll, maxHeight, autoScrollBottom, width, }) {
11
11
  const [isMounted, setIsMounted] = useState(false);
12
12
  const [modalContainer, setModalContainer] = useState(null);
13
13
  const [isAnimating, setIsAnimating] = useState(false);
@@ -123,7 +123,7 @@ function Dialog({ open, onOpenChange, disabled, trigger, title, subtitle, disabl
123
123
  maxHeight: maxHeight
124
124
  ? `min(${maxHeight}, calc(100% - var(--p-4)))`
125
125
  : null,
126
- }, children: [(title || subtitle || icon) && (jsx(CardHeader, { icon: icon, title: title, description: subtitle })), !disableCloseButton && (jsx("button", { className: S.dialogClose, onClick: handleCloseClick, "aria-label": "Close dialog", type: "button", children: jsx(XIcon, { size: 16 }) })), content && (jsx(CardContent, { className: contentClassName, noScroll: noScroll, autoScrollBottom: autoScrollBottom, children: content })), footer && (jsx(CardFooter, { className: cn(S.footer, S[`align-${footerAlignment}`], footerClassName), children: footer }))] })] }));
126
+ }, children: [(title || subtitle || icon) && (jsx(CardHeader, { icon: icon, title: title, description: subtitle })), !disableCloseButton && (jsx("button", { className: S.dialogClose, onClick: handleCloseClick, "aria-label": "Close dialog", type: "button", children: jsx(XIcon, { size: 16 }) })), content && (jsx(CardContent, { className: contentClassName, yScrollbarClassName: contentScrollbarClassName, noScroll: noScroll, autoScrollBottom: autoScrollBottom, children: content })), footer && (jsx(CardFooter, { className: cn(S.footer, S[`align-${footerAlignment}`], footerClassName), children: footer }))] })] }));
127
127
  const onTriggerClick = !disabled ? () => onOpenChange(true) : undefined;
128
128
  return (jsxs(Fragment, { children: [trigger && jsx("div", { onClick: onTriggerClick, children: trigger }), open &&
129
129
  !disabled &&
@@ -7,10 +7,10 @@ import { getForecastColor } from '../../ui/ChartAreaInteractive/ChartLines.js';
7
7
  import '../../ui/Page/AppShell/AppShell.js';
8
8
  import 'react-router-dom';
9
9
  import '../../ui/Sidebar/Sidebar.js';
10
- import 'lucide-react';
10
+ import { InfoIcon } from 'lucide-react';
11
11
  import '../../ui/Page/Breadcrumbs/Breadcrumbs.styl.js';
12
12
  import '../../ui/Page/pageContext.js';
13
- import '../../ui/Tooltip/Tooltip.js';
13
+ import { Tooltip, TooltipTrigger, TooltipContent } from '../../ui/Tooltip/Tooltip.js';
14
14
  import '../../ui/Page/PageHeader/PageHeader.styl.js';
15
15
  import '../../ui/Page/PageEmptyCanvas/PageEmptyCanvas.styl.js';
16
16
  import '../../ui/Page/PageContent/PageContent.styl.js';
@@ -24,7 +24,7 @@ import '../../ui/Tabs/Tabs.styl.js';
24
24
  import '../../ui/Page/PageTabs/PageTabs.styl.js';
25
25
  import '../../ui/Page/PageColumns/PageColumns.styl.js';
26
26
  import '../../ui/Page/SectionHeader/SectionHeader.styl.js';
27
- import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../ui/Table/Table.js';
27
+ import { Table, TableHeader, TableRow, TableHead, TableCellValue, TableBody, TableCell } from '../../ui/Table/Table.js';
28
28
  import { TextShimmer } from '../../ui/TextShimmer/TextShimmer.js';
29
29
  import { TIME_RANGES } from '../../ui/TimeRangeControls/TimeRangeControls.constants.js';
30
30
  import S from './DriversComparisonChart.styl.js';
@@ -127,7 +127,7 @@ function DriversComparisonChart({ payload, datasetHistorical = [], loading = fal
127
127
  const showEmptyOverlay = (runAnalysisHint || Boolean(statusHint)) && !loading;
128
128
  return (jsxs("div", { className: cn(S.root, className), children: [jsxs("div", { className: cn(S.chartShell, loading && S.chartShellLoading), children: [jsx("div", { className: S.chartSlot, children: jsxs("div", { className: S.chartWithOverlay, children: [jsx("div", { className: cn(S.chartInteractiveLayer, showEmptyOverlay && S.chartInteractiveDimmed), children: jsx(ChartAreaInteractive, { disableHistoricalAnimation: true, disableTimeRangeSelector: true, enableTimeRangeBrush: true, chartContainerClassName: S.chartContainer, chartData: driversComparisonChartData, forecastData: chartForecastData, timeRange: timeRange, onTimeRangeChange: handleTimeRangeChange, pinMonth: undefined, onPinMonthChange: () => { }, isDarkTheme: isDarkTheme, loading: chartLoading, hasCombinedData: mergedWithHistorical.length > 0, forecastLineStyle: "solid", showLegend: false, hiddenSeries: hiddenSeries, toggleLegendSeries: toggleSeries, ensureAnalysisSeriesVisible: showSeries }) }), showEmptyOverlay && (jsx("div", { className: S.chartEmptyOverlay, role: "status", "aria-live": "polite", children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", hint: runAnalysisHint
129
129
  ? 'Run a completed analysis to load the drivers comparison chart for an analysis.'
130
- : undefined, status: statusHint ?? undefined, statusTone: statusTone }) }) }))] }) }), loading && (jsx("div", { className: S.loadingLayer, "aria-busy": "true", "aria-live": "polite", children: jsx("div", { className: S.loadingMessage, children: jsx(TextShimmer, { as: "span", className: S.loadingText, children: "Loading drivers comparison\u2026" }) }) }))] }), jsx("div", { className: S.seriesSection, children: tableSeriesRows.length === 0 ? (jsx("div", { className: S.seriesEmptyWrap, children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", align: "center", status: "No series" }) }) })) : (jsx("div", { className: S.seriesTableWrapper, children: jsx(PageXScroll, { size: "md", fullWidth: true, innerClassName: S.seriesTableContainer, scrollbarClassName: S.seriesScrollbar, children: jsxs(Table, { withBackground: true, withPaddings: true, className: S.seriesTable, children: [jsx(TableHeader, { children: jsxs(TableRow, { children: [jsx(TableHead, { className: S.seriesColSeries, children: "Driver name" }), jsx(TableHead, { children: "Importance" }), jsx(TableHead, { children: "Lag" })] }) }), jsx(TableBody, { children: tableSeriesRows.map(row => {
130
+ : undefined, status: statusHint ?? undefined, statusTone: statusTone }) }) }))] }) }), loading && (jsx("div", { className: S.loadingLayer, "aria-busy": "true", "aria-live": "polite", children: jsx("div", { className: S.loadingMessage, children: jsx(TextShimmer, { as: "span", className: S.loadingText, children: "Loading drivers comparison\u2026" }) }) }))] }), jsx("div", { className: S.seriesSection, children: tableSeriesRows.length === 0 ? (jsx("div", { className: S.seriesEmptyWrap, children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", align: "center", status: "No series" }) }) })) : (jsx("div", { className: S.seriesTableWrapper, children: jsx(PageXScroll, { size: "md", fullWidth: true, innerClassName: S.seriesTableContainer, scrollbarClassName: S.seriesScrollbar, children: jsxs(Table, { withBackground: true, withPaddings: true, className: S.seriesTable, children: [jsx(TableHeader, { children: jsxs(TableRow, { children: [jsx(TableHead, { className: S.seriesColSeries, children: "Driver name" }), jsx(TableHead, { children: jsxs(TableCellValue, { children: ["Importance", jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: jsx("span", { children: jsx(InfoIcon, { size: 16, style: { cursor: 'help' } }) }) }), jsx(TooltipContent, { side: "top", maxWidth: 300, children: "How much this driver contributes to price movements in the forecast model. Higher = stronger influence. Reflects relative weight of each driver, scored from 0% to 100%." })] })] }) }), jsx(TableHead, { children: jsxs(TableCellValue, { children: ["Lag", jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: jsx("span", { children: jsx(InfoIcon, { size: 16, style: { cursor: 'help' } }) }) }), jsx(TooltipContent, { side: "top", maxWidth: 300, children: "Lag shows how far ahead this driver predicts price movement. A range (e.g. 9\u201312 months) means the predictive signal is strongest somewhere in that window \u2014 not at a single fixed point." })] })] }) })] }) }), jsx(TableBody, { children: tableSeriesRows.map(row => {
131
131
  const dataKey = toForecastDataKey(row.id);
132
132
  const hidden = hiddenSeries.has(dataKey);
133
133
  return (jsxs(TableRow, { className: cn(hidden && S.rowHidden), onClick: () => toggleSeries(row.id), children: [jsx(TableCell, { children: jsxs("span", { className: S.seriesLabel, children: [jsx("span", { className: S.colorSwatch, style: {
@@ -6,6 +6,6 @@ declare function CardHeader({ className, icon, iconWithBackground, textsClassNam
6
6
  declare function CardTitle({ className, ...props }: CardTitleProps): import("react/jsx-runtime").JSX.Element;
7
7
  declare function CardDescription({ className, ...props }: CardDescriptionProps): import("react/jsx-runtime").JSX.Element;
8
8
  declare function CardAction({ className, ...props }: CardActionProps): import("react/jsx-runtime").JSX.Element;
9
- declare function CardContent({ className, centered, fullHeight, noScroll, children, autoScrollBottom, ...props }: CardContentProps): import("react/jsx-runtime").JSX.Element;
9
+ declare function CardContent({ className, centered, fullHeight, noScroll, children, autoScrollBottom, yScrollbarClassName, ...props }: CardContentProps): import("react/jsx-runtime").JSX.Element;
10
10
  declare function CardFooter({ className, ...props }: CardFooterProps): import("react/jsx-runtime").JSX.Element;
11
11
  export { Card, LinkCard, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
@@ -24,6 +24,7 @@ export interface CardContentProps extends React.ComponentProps<'div'> {
24
24
  fullHeight?: boolean;
25
25
  noScroll?: boolean;
26
26
  autoScrollBottom?: boolean;
27
+ yScrollbarClassName?: string;
27
28
  }
28
29
  export interface CardFooterProps extends React.ComponentProps<'div'> {
29
30
  }
@@ -1,3 +1,3 @@
1
1
  import type { DialogProps } from './Dialog.types';
2
- export declare function Dialog({ open, onOpenChange, disabled, trigger, title, subtitle, disableCloseButton, icon, content, contentClassName, footer, footerClassName, footerAlignment, size, className, noScroll, maxHeight, autoScrollBottom, width, }: DialogProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function Dialog({ open, onOpenChange, disabled, trigger, title, subtitle, disableCloseButton, icon, content, contentClassName, contentScrollbarClassName, footer, footerClassName, footerAlignment, size, className, noScroll, maxHeight, autoScrollBottom, width, }: DialogProps): import("react/jsx-runtime").JSX.Element;
3
3
  export { default as DialogStyles } from './Dialog.styl';
@@ -14,6 +14,7 @@ export interface DialogProps {
14
14
  icon?: ReactNode;
15
15
  content?: ReactNode;
16
16
  contentClassName?: string;
17
+ contentScrollbarClassName?: string;
17
18
  footer?: ReactNode;
18
19
  footerClassName?: string;
19
20
  footerAlignment?: 'left' | 'center' | 'right' | 'between';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.53",
3
+ "version": "1.3.54",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -129,9 +129,10 @@ function CardContent({
129
129
  noScroll,
130
130
  children,
131
131
  autoScrollBottom,
132
+ yScrollbarClassName,
132
133
  ...props
133
134
  }: CardContentProps) {
134
- const contentRef = useRef<HTMLDivElement>(null);
135
+ const scrollInnerRef = useRef<HTMLDivElement | null>(null);
135
136
 
136
137
  const contentDiv = (
137
138
  <div
@@ -149,7 +150,7 @@ function CardContent({
149
150
 
150
151
  useEffect(() => {
151
152
  if (!noScroll && autoScrollBottom) {
152
- const inner = contentRef.current?.firstChild as HTMLDivElement;
153
+ const inner = scrollInnerRef.current;
153
154
 
154
155
  if (!inner) return;
155
156
 
@@ -170,8 +171,16 @@ function CardContent({
170
171
  if (noScroll) return contentDiv;
171
172
 
172
173
  return (
173
- // @ts-ignore
174
- <Scroll y autoHide fadeSize="m" className={S.scroll} ref={contentRef}>
174
+ <Scroll
175
+ y
176
+ autoHide
177
+ fadeSize="m"
178
+ className={S.scroll}
179
+ yScrollbarClassName={yScrollbarClassName}
180
+ onInnerRef={el => {
181
+ scrollInnerRef.current = el;
182
+ }}
183
+ >
175
184
  {contentDiv}
176
185
  </Scroll>
177
186
  );
@@ -30,6 +30,7 @@ export interface CardContentProps extends React.ComponentProps<'div'> {
30
30
  fullHeight?: boolean;
31
31
  noScroll?: boolean;
32
32
  autoScrollBottom?: boolean;
33
+ yScrollbarClassName?: string;
33
34
  }
34
35
 
35
36
  export interface CardFooterProps extends React.ComponentProps<'div'> {}
@@ -31,6 +31,7 @@ export function Dialog({
31
31
  icon,
32
32
  content,
33
33
  contentClassName,
34
+ contentScrollbarClassName,
34
35
  footer,
35
36
  footerClassName,
36
37
  footerAlignment = 'center',
@@ -215,6 +216,7 @@ export function Dialog({
215
216
  {content && (
216
217
  <CardContent
217
218
  className={contentClassName}
219
+ yScrollbarClassName={contentScrollbarClassName}
218
220
  noScroll={noScroll}
219
221
  autoScrollBottom={autoScrollBottom}
220
222
  >
@@ -15,6 +15,7 @@ export interface DialogProps {
15
15
  icon?: ReactNode;
16
16
  content?: ReactNode;
17
17
  contentClassName?: string;
18
+ contentScrollbarClassName?: string;
18
19
  footer?: ReactNode;
19
20
  footerClassName?: string;
20
21
  footerAlignment?: 'left' | 'center' | 'right' | 'between';
@@ -13,13 +13,20 @@ import {
13
13
  Table,
14
14
  TableBody,
15
15
  TableCell,
16
+ TableCellValue,
16
17
  TableHead,
17
18
  TableHeader,
18
19
  TableRow,
19
20
  } from '#uilib/components/ui/Table';
20
21
  import { TextShimmer } from '#uilib/components/ui/TextShimmer/TextShimmer';
21
22
  import { TIME_RANGES } from '#uilib/components/ui/TimeRangeControls/TimeRangeControls.constants';
23
+ import {
24
+ Tooltip,
25
+ TooltipContent,
26
+ TooltipTrigger,
27
+ } from '#uilib/components/ui/Tooltip';
22
28
  import type { BacktestsComponentPayload } from '@sybilion/platform-sdk';
29
+ import { InfoIcon } from 'lucide-react';
23
30
 
24
31
  import S from './DriversComparisonChart.styl';
25
32
  import {
@@ -282,8 +289,42 @@ export function DriversComparisonChart({
282
289
  <TableHead className={S.seriesColSeries}>
283
290
  Driver name
284
291
  </TableHead>
285
- <TableHead>Importance</TableHead>
286
- <TableHead>Lag</TableHead>
292
+ <TableHead>
293
+ <TableCellValue>
294
+ Importance
295
+ <Tooltip>
296
+ <TooltipTrigger asChild>
297
+ <span>
298
+ <InfoIcon size={16} style={{ cursor: 'help' }} />
299
+ </span>
300
+ </TooltipTrigger>
301
+ <TooltipContent side="top" maxWidth={300}>
302
+ How much this driver contributes to price movements
303
+ in the forecast model. Higher = stronger influence.
304
+ Reflects relative weight of each driver, scored from
305
+ 0% to 100%.
306
+ </TooltipContent>
307
+ </Tooltip>
308
+ </TableCellValue>
309
+ </TableHead>
310
+ <TableHead>
311
+ <TableCellValue>
312
+ Lag
313
+ <Tooltip>
314
+ <TooltipTrigger asChild>
315
+ <span>
316
+ <InfoIcon size={16} style={{ cursor: 'help' }} />
317
+ </span>
318
+ </TooltipTrigger>
319
+ <TooltipContent side="top" maxWidth={300}>
320
+ Lag shows how far ahead this driver predicts price
321
+ movement. A range (e.g. 9–12 months) means the
322
+ predictive signal is strongest somewhere in that
323
+ window — not at a single fixed point.
324
+ </TooltipContent>
325
+ </Tooltip>
326
+ </TableCellValue>
327
+ </TableHead>
287
328
  </TableRow>
288
329
  </TableHeader>
289
330
  <TableBody>