added some additional packages for reports

This commit is contained in:
Nate Kelley 2025-09-01 22:26:08 -06:00
parent f7a8572a8e
commit 27bdd9bbe4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
17 changed files with 922 additions and 138 deletions

View File

@ -38,6 +38,10 @@
"@faker-js/faker": "^10.0.0",
"@modelcontextprotocol/sdk": "^1.17.4",
"@monaco-editor/react": "^4.7.0",
"@platejs/dnd": "^49.2.10",
"@platejs/resizable": "^49.0.0",
"@platejs/selection": "^49.2.4",
"@platejs/table": "catalog:",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
@ -53,6 +57,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toolbar": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/react-visually-hidden": "^1.2.3",
"@shikijs/langs": "^3.12.1",

View File

@ -140,9 +140,11 @@ export const useGetMetricData = <TData = BusterMetricDataExtended>(
{
id = '',
versionNumber: versionNumberProp,
reportFileId,
}: {
id: string | undefined;
versionNumber?: number | 'LATEST';
reportFileId?: string;
},
params?: Omit<UseQueryOptions<BusterMetricData, RustApiError, TData>, 'queryKey' | 'queryFn'>
) => {
@ -167,6 +169,7 @@ export const useGetMetricData = <TData = BusterMetricDataExtended>(
id,
version_number: chosenVersionNumber || undefined,
password,
report_file_id: reportFileId,
});
const isLatest = versionNumberProp === 'LATEST' || !versionNumberProp;
if (isLatest) {

View File

@ -39,11 +39,10 @@ export const getMetric = async ({
export const getMetricData = async ({
id,
version_number,
password,
...params
}: GetMetricDataRequest): Promise<MetricDataResponse> => {
return mainApi
.get<MetricDataResponse>(`/metric_files/${id}/data`, { params: { password, version_number } })
.get<MetricDataResponse>(`/metric_files/${id}/data`, { params })
.then((res) => res.data);
};

View File

@ -1,5 +1,5 @@
import type { VerificationStatus } from '@buster/server-shared/share';
import { Link, useNavigate } from '@tanstack/react-router';
import { useNavigate } from '@tanstack/react-router';
import React, { useMemo } from 'react';
import {
useAddMetricsToDashboard,
@ -17,6 +17,8 @@ import { useSaveToCollectionsDropdownContent } from '@/components/features/dropd
import { useSaveToDashboardDropdownContent } from '@/components/features/dropdowns/SaveToDashboardDropdown';
import { StatusBadgeIndicator } from '@/components/features/metrics/StatusBadgeIndicator';
import { useStatusDropdownContent } from '@/components/features/metrics/StatusBadgeIndicator/useStatusDropdownContent';
import { useDownloadMetricDataCSV } from '@/components/features/metrics/useDownloadMetricDataCSV';
import { useDownloadPNGSelectMenu } from '@/components/features/metrics/useDownloadMetricDataPNG';
import { getShareAssetConfig } from '@/components/features/ShareMenu/helpers';
import { ShareMenuContent } from '@/components/features/ShareMenu/ShareMenuContent';
import { Button } from '@/components/ui/buttons';
@ -38,8 +40,6 @@ import {
} from '@/components/ui/icons';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { useIsChatMode } from '@/context/Chats/useMode';
import { useDownloadMetricDataCSV } from '@/context/Metrics/useDownloadMetricDataCSV';
import { useDownloadPNGSelectMenu } from '@/context/Metrics/useDownloadMetricDataPNG';
import { useMetricEditToggle } from '@/layouts/AssetContainer/MetricAssetContainer';
import { canEdit, getIsEffectiveOwner, getIsOwner } from '@/lib/share';
import { ASSET_ICONS } from '../icons/assetIcons';

View File

@ -1,19 +1,22 @@
import { useNavigate } from '@tanstack/react-router';
import React, { useCallback, useMemo } from 'react';
import { useGetMetric } from '@/api/buster_rest/metrics';
import React, { useCallback, useMemo, useState } from 'react';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import {
createDropdownItem,
DropdownContent,
type IDropdownItem,
type IDropdownItems,
} from '@/components/ui/dropdown';
import { History, Pencil, Star, WandSparkle } from '@/components/ui/icons';
import { Download4, History, Pencil, SquareChart, Star, WandSparkle } from '@/components/ui/icons';
import { Star as StarFilled } from '@/components/ui/icons/NucleoIconFilled';
import type { BusterMetric } from '../../../api/asset_interfaces/metric';
import { useBusterNotifications } from '../../../context/BusterNotifications';
import { ensureElementExists } from '../../../lib/element';
import { downloadElementToImage } from '../../../lib/exportUtils';
import { FollowUpWithAssetContent } from '../assets/FollowUpWithAsset';
import { useFavoriteStar } from '../favorites';
import { useListMetricVersionDropdownItems } from '../versionHistory/useListMetricVersionDropdownItems';
import { METRIC_CHART_CONTAINER_ID } from './MetricChartCard/config';
import { METRIC_CHART_TITLE_INPUT_ID } from './MetricChartCard/MetricViewChartHeader';
export const useMetricVersionHistorySelectMenu = ({
@ -132,3 +135,81 @@ export const useRenameMetricOnPage = ({
[navigate, metricId, metricVersionNumber]
);
};
export const useDownloadMetricDataCSV = ({
metricId,
metricVersionNumber,
}: {
metricId: string;
metricVersionNumber: number | undefined;
}) => {
const [isDownloading, setIsDownloading] = useState(false);
const { data: metricData } = useGetMetricData(
{ id: metricId, versionNumber: metricVersionNumber },
{ enabled: false }
);
const { data: name } = useGetMetric({ id: metricId }, { select: (x) => x.name });
return useMemo(
() => ({
label: 'Download as CSV',
value: 'download-csv',
icon: <Download4 />,
loading: isDownloading,
onClick: async () => {
const data = metricData?.data;
if (data && name) {
setIsDownloading(true);
const exportJSONToCSV = await import('@/lib/exportUtils').then(
(mod) => mod.exportJSONToCSV
);
await exportJSONToCSV(data, name);
setIsDownloading(false);
}
},
}),
[metricData, isDownloading, name]
);
};
export const useDownloadPNGSelectMenu = ({
metricId,
metricVersionNumber,
}: {
metricId: string;
metricVersionNumber: number | undefined;
}) => {
const { openErrorMessage } = useBusterNotifications();
const { data: name } = useGetMetric(
{ id: metricId, versionNumber: metricVersionNumber },
{ select: (x) => x.name }
);
const { data: selectedChartType } = useGetMetric(
{ id: metricId },
{ select: (x) => x.chart_config?.selectedChartType }
);
const canDownload = selectedChartType && selectedChartType !== 'table';
return useMemo(
() => ({
label: 'Download as PNG',
value: 'download-png',
disabled: true,
icon: <SquareChart />,
onClick: async () => {
const node = document.getElementById(METRIC_CHART_CONTAINER_ID(metricId)) as HTMLElement;
if (node) {
try {
return await downloadElementToImage(node, `${name}.png`);
} catch (error) {
console.error(error);
}
}
openErrorMessage('Failed to download PNG');
},
}),
[canDownload, metricId, name, openErrorMessage]
);
};

View File

@ -0,0 +1,237 @@
'use client';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import type * as React from 'react';
import { cn } from '@/lib/utils';
import { THEME_RESET_STYLE } from '@/styles/theme-reset';
import { Check3 as Check, ChevronRight } from '../icons/NucleoIconOutlined';
import RadioUnchecked from '../icons/NucleoIconOutlined/radio-unchecked';
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
}
function DropdownMenuContent({
className,
sideOffset = 4,
style,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className
)}
style={{ ...THEME_RESET_STYLE, ...style }}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
}
function DropdownMenuItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: 'default' | 'destructive';
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<div className="text-icon-color size-4">
<Check />
</div>
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<div className="text-icon-color size-4">
<RadioUnchecked />
</div>
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', className)}
{...props}
/>
);
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
}
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
className
)}
{...props}
>
{children}
<div className="text-icon-color ml-auto size-4">
<ChevronRight />
</div>
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className
)}
{...props}
/>
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};

View File

@ -0,0 +1 @@
export * from './dropdown-menu';

View File

@ -1,8 +1,6 @@
import { getEquationHtml } from '@platejs/math';
import type { SlateElementProps, TEquationElement } from 'platejs';
import { SlateElement } from 'platejs';
import * as React from 'react';
import { cn } from '@/lib/utils';
import { NodeTypeIcons } from '../config/icons';
@ -86,6 +84,7 @@ export function InlineEquationElementStatic(props: SlateElementProps<TEquationEl
props.element.texExpression.length === 0 && 'hidden',
'font-mono leading-none'
)}
// biome-ignore lint/security/noDangerouslySetInnerHtml: no other option
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>

View File

@ -1,10 +1,8 @@
import React, { useMemo, useRef } from 'react';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import { MetricCard } from '@/components/ui/metric';
import React, { useRef } from 'react';
import { MetricChartCard } from '@/components/features/metrics/MetricChartCard';
import { useInViewport } from '@/hooks/useInViewport';
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
import { cn } from '@/lib/utils';
import { useGetChatId } from '../../../../../context/Chats/useGetChatId';
import { useGetReportParams } from '../../../../../context/Reports/useGetReportParams';
import { useMetricContentThreeDotMenuItems } from './useMetricContentThreeDotMenuItems';
export const MetricContent = React.memo(
@ -13,7 +11,6 @@ export const MetricContent = React.memo(
metricVersionNumber,
isExportMode = false,
readOnly = false,
className,
}: {
metricId: string;
metricVersionNumber: number | undefined;
@ -21,52 +18,19 @@ export const MetricContent = React.memo(
isExportMode?: boolean;
className?: string;
}) => {
const chatId = useChatLayoutContextSelector((x) => x.chatId);
const reportId = useChatLayoutContextSelector((x) => x.reportId) || '';
const reportVersionNumber = useChatLayoutContextSelector((x) => x.reportVersionNumber);
const chatId = useGetChatId();
const { reportId, reportVersionNumber } = useGetReportParams();
const ref = useRef<HTMLDivElement>(null);
const hasBeenInViewport = useRef(false);
const [inViewport] = useInViewport(ref, {
threshold: 0.33,
threshold: 0.25,
});
if (inViewport && !hasBeenInViewport.current) {
hasBeenInViewport.current = true;
}
const renderChart = inViewport || isExportMode || hasBeenInViewport.current;
const {
data: metric,
isFetched: isFetchedMetric,
error: metricError,
} = useGetMetric(
{
id: metricId,
versionNumber: metricVersionNumber,
},
{ enabled: !!metricId }
);
const {
data: metricData,
isFetched: isFetchedMetricData,
error: metricDataError,
} = useGetMetricData({
id: metricId,
versionNumber: metricVersionNumber,
reportFileId: reportId || undefined,
});
const link = useMemo(() => {
return assetParamsToRoute({
type: 'metric',
chatId,
assetId: metricId,
reportId,
metricId,
versionNumber: metricVersionNumber || undefined,
});
}, [chatId, reportId, metricId, metricVersionNumber]);
const threeDotMenuItems = useMetricContentThreeDotMenuItems({
metricId,
metricVersionNumber,
@ -75,24 +39,13 @@ export const MetricContent = React.memo(
chatId,
});
const error: string | undefined =
metric?.error || metricDataError?.message || metricError?.message || undefined;
return (
<MetricCard
className={cn('transition-all duration-100', className)}
ref={ref}
metricLink={link}
animate={!isExportMode}
<MetricChartCard
metricId={metricId}
readOnly={readOnly}
isDragOverlay={false}
metric={metric}
metricData={metricData}
renderChart={renderChart}
loading={(!isFetchedMetric || !isFetchedMetricData) && !metricError}
error={error}
threeDotMenuItems={threeDotMenuItems}
useHeaderLink={!readOnly}
versionNumber={metricVersionNumber}
readOnly
headerSecondaryContent={!readOnly && <div>TODO: Three dot menu</div>}
/>
);
}

View File

@ -1,13 +1,18 @@
import type { TElement } from 'platejs';
import { type PlateEditor, useEditorRef, useElement } from 'platejs/react';
import React, { useMemo } from 'react';
import type { DropdownItem, DropdownItems } from '@/components/ui/dropdown';
import { ArrowUpRight, Code, SquareChartPen, Table, Trash } from '@/components/ui/icons';
import {
useDownloadMetricDataCSV,
useDownloadPNGSelectMenu,
useRenameMetricOnPage,
} from '@/context/Metrics/metricDropdownItems';
} from '@/components/features/metrics/threeDotMenuHooks';
import {
createDropdownItem,
createDropdownItems,
type IDropdownItem,
type IDropdownItems,
} from '@/components/ui/dropdown';
import { ArrowUpRight, Code, SquareChartPen, Table, Trash } from '@/components/ui/icons';
import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
export const useMetricContentThreeDotMenuItems = ({
@ -22,7 +27,7 @@ export const useMetricContentThreeDotMenuItems = ({
metricVersionNumber: number | undefined;
reportVersionNumber: number | undefined;
reportId: string;
}): DropdownItems => {
}): IDropdownItems => {
const editor = useEditorRef();
const element = useElement();
@ -72,10 +77,7 @@ export const useMetricContentThreeDotMenuItems = ({
};
const useOpenChartItem = ({
reportId,
metricId,
chatId,
reportVersionNumber,
metricVersionNumber,
}: {
reportId: string;
@ -83,25 +85,25 @@ const useOpenChartItem = ({
metricVersionNumber: number | undefined;
chatId: string | undefined;
reportVersionNumber: number | undefined;
}): DropdownItem => {
const route = assetParamsToRoute({
assetId: metricId,
type: 'metric',
reportVersionNumber,
metricVersionNumber,
reportId,
metricId,
chatId,
});
}): IDropdownItem => {
return useMemo(
() => ({
value: 'open-chart',
label: 'Open chart',
icon: <ArrowUpRight />,
link: route,
linkIcon: 'arrow-right',
}),
[route]
() =>
createDropdownItem({
value: 'open-chart',
label: 'Open chart',
icon: <ArrowUpRight />,
link: {
to: '/app/metrics/$metricId/chart',
params: {
metricId,
},
search: {
metric_version_number: metricVersionNumber,
},
},
linkIcon: 'arrow-right',
}),
[]
);
};
@ -111,17 +113,18 @@ const useRemoveFromReportItem = ({
}: {
editor: PlateEditor;
element: TElement;
}): DropdownItem => {
}): IDropdownItem => {
return useMemo(
() => ({
value: 'remove-from-report',
label: 'Remove from report',
icon: <Trash />,
onClick: () => {
const path = editor.api.findPath(element);
editor.tf.removeNodes({ at: path });
},
}),
() =>
createDropdownItem({
value: 'remove-from-report',
label: 'Remove from report',
icon: <Trash />,
onClick: () => {
const path = editor.api.findPath(element);
editor.tf.removeNodes({ at: path });
},
}),
[]
);
};
@ -138,36 +141,51 @@ const useNavigatetoMetricItem = ({
metricVersionNumber: number | undefined;
chatId: string | undefined;
reportVersionNumber: number | undefined;
}): DropdownItem[] => {
}): IDropdownItem[] => {
return useMemo(() => {
const baseParams = {
assetId: metricId,
type: 'metric' as const,
reportVersionNumber,
metricVersionNumber,
reportId,
chatId,
};
const editChartRoute = assetParamsToRoute({
...baseParams,
page: 'chart',
});
const resultsChartRoute = assetParamsToRoute({
...baseParams,
page: 'results',
});
const sqlChartRoute = assetParamsToRoute({
...baseParams,
page: 'sql',
});
return [
{ value: 'edit-chart', label: 'Edit chart', icon: <SquareChartPen />, link: editChartRoute },
{ value: 'results-chart', label: 'Results chart', icon: <Table />, link: resultsChartRoute },
{ value: 'sql-chart', label: 'SQL chart', icon: <Code />, link: sqlChartRoute },
];
return createDropdownItems([
{
value: 'edit-chart',
label: 'Edit chart',
icon: <SquareChartPen />,
link: {
to: '/app/metrics/$metricId/chart',
params: {
metricId,
},
search: {
metric_version_number: metricVersionNumber,
},
},
},
{
value: 'results-chart',
label: 'Results chart',
icon: <Table />,
link: {
to: '/app/metrics/$metricId/results',
params: {
metricId,
},
search: {
metric_version_number: metricVersionNumber,
},
},
},
{
value: 'sql-chart',
label: 'SQL chart',
icon: <Code />,
link: {
to: '/app/metrics/$metricId/sql',
params: {
metricId,
},
search: {
metric_version_number: metricVersionNumber,
},
},
},
]);
}, [reportId, metricId, chatId, reportVersionNumber, metricVersionNumber]);
};

View File

@ -0,0 +1,370 @@
'use client';
import * as ToolbarPrimitive from '@radix-ui/react-toolbar';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import {
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuSeparator,
} from '@/components/ui/dropdown-menu';
import { ChevronDown } from '@/components/ui/icons/NucleoIconOutlined';
import { Separator } from '@/components/ui/separator';
import { TooltipBase as Tooltip, TooltipTrigger } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
export const Toolbar = React.forwardRef<
React.ElementRef<typeof ToolbarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root>
>(function Toolbar({ className, ...props }, ref) {
return (
<ToolbarPrimitive.Root
ref={ref}
className={cn('relative flex items-center select-none', className)}
{...props}
/>
);
});
export function ToolbarToggleGroup({
className,
...props
}: React.ComponentProps<typeof ToolbarPrimitive.ToolbarToggleGroup>) {
return (
<ToolbarPrimitive.ToolbarToggleGroup
className={cn('flex items-center', className)}
{...props}
/>
);
}
export function ToolbarLink({
className,
...props
}: React.ComponentProps<typeof ToolbarPrimitive.Link>) {
return (
<ToolbarPrimitive.Link
className={cn('font-medium underline underline-offset-4', className)}
{...props}
/>
);
}
export function ToolbarSeparator({
className,
...props
}: React.ComponentProps<typeof ToolbarPrimitive.Separator>) {
return (
<ToolbarPrimitive.Separator
className={cn('bg-border mx-2 my-1 w-px shrink-0', className)}
{...props}
/>
);
}
// From toggleVariants
const toolbarButtonVariants = cva(
"inline-flex cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-checked:bg-accent aria-checked:text-accent-foreground aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
defaultVariants: {
size: 'default',
variant: 'default',
},
variants: {
size: {
default: 'h-9 min-w-9 px-2',
lg: 'h-10 min-w-10 px-2.5',
sm: 'h-8 min-w-8 px-1.5',
},
variant: {
default: 'bg-transparent',
outline:
'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
},
},
}
);
const dropdownArrowVariants = cva(
cn(
'inline-flex items-center justify-center rounded-r-md text-sm font-medium text-foreground transition-colors disabled:pointer-events-none disabled:opacity-50'
),
{
defaultVariants: {
size: 'sm',
variant: 'default',
},
variants: {
size: {
default: 'h-9 w-6',
lg: 'h-10 w-8',
sm: 'h-8 w-4',
},
variant: {
default:
'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',
outline:
'border border-l-0 border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
},
},
}
);
export function ToolbarToggleItem({
className,
size = 'sm',
variant,
...props
}: React.ComponentProps<typeof ToolbarPrimitive.ToggleItem> &
VariantProps<typeof toolbarButtonVariants>) {
return (
<ToolbarPrimitive.ToggleItem
className={cn(toolbarButtonVariants({ size, variant }), className)}
{...props}
/>
);
}
type ToolbarButtonProps = {
isDropdown?: boolean;
pressed?: boolean;
} & Omit<React.ComponentPropsWithoutRef<typeof ToolbarToggleItem>, 'asChild' | 'value'> &
VariantProps<typeof toolbarButtonVariants>;
const ToolbarButtonBase = React.forwardRef<
React.ElementRef<typeof ToolbarPrimitive.Button>,
ToolbarButtonProps
>(function ToolbarButtonBase(
{ children, className, isDropdown, pressed, size = 'sm', variant, ...props },
ref
) {
return typeof pressed === 'boolean' ? (
<ToolbarToggleGroup disabled={props.disabled} value="single" type="single">
<ToolbarToggleItem
className={cn(
toolbarButtonVariants({
size,
variant,
}),
isDropdown && 'justify-between gap-1 pr-1',
className
)}
value={pressed ? 'single' : ''}
{...props}
>
{isDropdown ? (
<>
<div className="flex flex-1 items-center gap-2 whitespace-nowrap">{children}</div>
<div className="text-muted-foreground size-3.5">
<ChevronDown data-icon />
</div>
</>
) : (
children
)}
</ToolbarToggleItem>
</ToolbarToggleGroup>
) : (
<ToolbarPrimitive.Button
ref={ref}
className={cn(
toolbarButtonVariants({
size,
variant,
}),
isDropdown && 'pr-1',
className
)}
{...props}
>
{children}
</ToolbarPrimitive.Button>
);
});
export const ToolbarButton = withTooltip(ToolbarButtonBase);
export function ToolbarSplitButton({
className,
...props
}: React.ComponentPropsWithoutRef<typeof ToolbarButton>) {
return (
<ToolbarButton
className={cn('group flex gap-0 px-0 hover:bg-transparent', className)}
{...props}
/>
);
}
type ToolbarSplitButtonPrimaryProps = Omit<
React.ComponentPropsWithoutRef<typeof ToolbarToggleItem>,
'value'
> &
VariantProps<typeof toolbarButtonVariants>;
export function ToolbarSplitButtonPrimary({
children,
className,
size = 'sm',
variant,
...props
}: ToolbarSplitButtonPrimaryProps) {
return (
<span
className={cn(
toolbarButtonVariants({
size,
variant,
}),
'rounded-r-none',
'group-data-[pressed=true]:bg-accent group-data-[pressed=true]:text-accent-foreground',
className
)}
{...props}
>
{children}
</span>
);
}
/**
* ToolbarSplitButtonSecondary is a functional component wrapped in React.forwardRef.
* It renders a styled <span> with a dropdown arrow, used as the secondary part of a split toolbar button.
* The ref is forwarded to the <span> element for parent access.
*/
export const ToolbarSplitButtonSecondary = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<'span'> & VariantProps<typeof dropdownArrowVariants>
>(function ToolbarSplitButtonSecondary({ className, size, variant, ...props }, ref) {
return (
<span
ref={ref}
className={cn(
dropdownArrowVariants({
size,
variant,
}),
'group-data-[pressed=true]:bg-accent group-data-[pressed=true]:text-accent-foreground',
className
)}
onClick={(e) => e.stopPropagation()}
{...props}
>
<div className="text-muted-foreground size-3.5">
<ChevronDown data-icon />
</div>
</span>
);
});
export function ToolbarGroup({ children, className }: React.ComponentProps<'div'>) {
return (
<div className={cn('group/toolbar-group', 'relative hidden has-[button]:flex', className)}>
<div className="flex items-center">{children}</div>
<div className="mx-1.5 py-0.5 group-last/toolbar-group:hidden!">
<Separator orientation="vertical" />
</div>
</div>
);
}
type TooltipProps<T extends React.ElementType> = {
tooltip?: React.ReactNode;
tooltipContentProps?: Omit<React.ComponentPropsWithoutRef<typeof TooltipContent>, 'children'>;
tooltipProps?: Omit<React.ComponentPropsWithoutRef<typeof Tooltip>, 'children'>;
tooltipTriggerProps?: React.ComponentPropsWithoutRef<typeof TooltipTrigger>;
} & React.ComponentProps<T>;
function withTooltip<T extends React.ElementType>(Component: T) {
return React.forwardRef<React.ElementRef<T>, TooltipProps<T>>(function ExtendComponent(
{ tooltip, tooltipContentProps, tooltipProps, tooltipTriggerProps, ...props },
ref
) {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
const component = React.createElement(Component, {
ref,
...(props as React.ComponentProps<T>),
});
if (tooltip && mounted) {
return (
<Tooltip {...tooltipProps}>
<TooltipTrigger asChild {...tooltipTriggerProps}>
{component}
</TooltipTrigger>
<TooltipContent {...tooltipContentProps}>{tooltip}</TooltipContent>
</Tooltip>
);
}
return component;
});
}
function TooltipContent({
children,
className,
// CHANGE
sideOffset = 4,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
className={cn(
'bg-primary text-primary-foreground z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
className
)}
data-slot="tooltip-content"
sideOffset={sideOffset}
{...props}
>
{children}
{/* CHANGE */}
{/* <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" /> */}
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
);
}
export function ToolbarMenuGroup({
children,
className,
label,
...props
}: React.ComponentProps<typeof DropdownMenuRadioGroup> & { label?: string }) {
return (
<>
<DropdownMenuSeparator
className={cn(
'hidden',
'mb-0 shrink-0 peer-has-[[role=menuitem]]/menu-group:block peer-has-[[role=menuitemradio]]/menu-group:block peer-has-[[role=option]]/menu-group:block'
)}
/>
<DropdownMenuRadioGroup
{...props}
className={cn(
'hidden',
'peer/menu-group group/menu-group my-1.5 has-[[role=menuitem]]:block has-[[role=menuitemradio]]:block has-[[role=option]]:block',
className
)}
>
{label && (
<DropdownMenuLabel className="text-muted-foreground text-xs font-semibold select-none">
{label}
</DropdownMenuLabel>
)}
{children}
</DropdownMenuRadioGroup>
</>
);
}

View File

@ -0,0 +1 @@
export * from './Toolbar';

View File

@ -1,10 +1,11 @@
import { z } from 'zod';
import { ShareRoleSchema, VerificationStatusSchema, WorkspaceShareRoleSchema } from '../share';
import { ShareRoleSchema, VerificationStatusSchema } from '../share';
import { ChartConfigPropsSchema } from './charts';
export const GetMetricRequestSchema = z.object({
id: z.string(),
password: z.string().optional(),
report_file_id: z.string().optional(),
version_number: z.number().optional(), //api will default to latest if not provided
});

View File

@ -43,7 +43,7 @@ catalogs:
specifier: ^49.0.0
version: 49.0.0
'@platejs/table':
specifier: ^49.1.13
specifier: 49.1.13
version: 49.1.13
'@platejs/toc':
specifier: ^49.0.0
@ -877,6 +877,18 @@ importers:
'@monaco-editor/react':
specifier: ^4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@platejs/dnd':
specifier: ^49.2.10
version: 49.2.10(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dnd-html5-backend@16.0.1)(react-dnd@16.0.1(@types/node@24.0.10)(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@platejs/resizable':
specifier: ^49.0.0
version: 49.0.0(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@platejs/selection':
specifier: ^49.2.4
version: 49.2.4(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@platejs/table':
specifier: 'catalog:'
version: 49.1.13(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-avatar':
specifier: ^1.1.10
version: 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
@ -922,6 +934,9 @@ importers:
'@radix-ui/react-tabs':
specifier: ^1.1.13
version: 1.1.13(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-toolbar':
specifier: ^1.1.10
version: 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-tooltip':
specifier: ^1.2.8
version: 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
@ -19136,6 +19151,16 @@ snapshots:
react-dnd-html5-backend: 16.0.1
react-dom: 18.3.1(react@18.3.1)
'@platejs/dnd@49.2.10(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dnd-html5-backend@16.0.1)(react-dnd@16.0.1(@types/node@24.0.10)(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
lodash: 4.17.21
platejs: 49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react))
raf: 3.4.1
react: link:apps/web-tss/@platejs/ai/react
react-dnd: 16.0.1(@types/node@24.0.10)(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
react-dnd-html5-backend: 16.0.1
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
'@platejs/docx@49.0.0(platejs@49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
platejs: 49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1))
@ -19254,6 +19279,12 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@platejs/resizable@49.0.0(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
platejs: 49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react))
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
'@platejs/selection@49.2.4(platejs@49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
copy-to-clipboard: 3.3.3
@ -19261,6 +19292,13 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@platejs/selection@49.2.4(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
copy-to-clipboard: 3.3.3
platejs: 49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react))
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
'@platejs/slash-command@49.0.0(platejs@49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@platejs/combobox': 49.0.0(platejs@49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -19292,6 +19330,14 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@platejs/table@49.1.13(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
'@platejs/resizable': 49.0.0(platejs@49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react)))(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
lodash: 4.17.21
platejs: 49.2.12(@types/react@19.1.12)(immer@10.1.1)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@apps+web-tss+@platejs+ai+react))
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
'@platejs/toc@49.0.0(platejs@49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
platejs: 49.2.12(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.26.0)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1))
@ -20079,6 +20125,23 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
optionalDependencies:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
'@radix-ui/primitive': 1.1.3
@ -20335,6 +20398,21 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-toggle-group@1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-toggle': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
optionalDependencies:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-toggle@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@ -20346,6 +20424,17 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-toggle@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
optionalDependencies:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-toolbar@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@ -20361,6 +20450,21 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-toolbar@1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-separator': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
'@radix-ui/react-toggle-group': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@apps+web-tss+@platejs+ai+react))(react@apps+web-tss+@platejs+ai+react)
react: link:apps/web-tss/@platejs/ai/react
react-dom: 19.1.1(react@apps+web-tss+@platejs+ai+react)
optionalDependencies:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-tooltip@1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@ -29847,6 +29951,18 @@ snapshots:
'@types/node': 24.0.10
'@types/react': 18.3.23
react-dnd@16.0.1(@types/node@24.0.10)(@types/react@19.1.12)(react@apps+web-tss+@platejs+ai+react):
dependencies:
'@react-dnd/invariant': 4.0.2
'@react-dnd/shallowequal': 4.0.2
dnd-core: 16.0.1
fast-deep-equal: 3.1.3
hoist-non-react-statics: 3.3.2
react: link:apps/web-tss/@platejs/ai/react
optionalDependencies:
'@types/node': 24.0.10
'@types/react': 19.1.12
react-docgen-typescript@2.4.0(typescript@5.9.2):
dependencies:
typescript: 5.9.2

View File

@ -20,7 +20,7 @@ catalog:
'@platejs/markdown': ^49.2.14
'@platejs/math': ^49.0.0
'@platejs/media': ^49.0.0
'@platejs/table': ^49.1.13
'@platejs/table': 49.1.13
'@platejs/toc': ^49.0.0
'@platejs/toggle': ^49.0.0
'@supabase/supabase-js': ^2.50.0