Compare commits

...

3 Commits

Author SHA1 Message Date
ahmadshaheer
1327ffbb8b chore: fix the tsc issues 2025-09-03 16:53:02 +04:30
ahmadshaheer
220c18fe66 fix: allow hovering spans even when the drawer is open 2025-09-03 16:31:43 +04:30
ahmadshaheer
c89e63e64a feat: implement side drawer in trace details v2 page 2025-09-03 15:02:06 +04:30
12 changed files with 260 additions and 183 deletions

View File

@@ -57,6 +57,7 @@
"@uiw/codemirror-theme-github": "4.24.1",
"@uiw/react-codemirror": "4.23.10",
"@uiw/react-md-editor": "3.23.5",
"@signozhq/drawer": "0.0.3",
"@visx/group": "3.3.0",
"@visx/hierarchy": "3.12.0",
"@visx/shape": "3.5.0",

View File

@@ -1,6 +1,10 @@
.attributes-corner {
display: flex;
flex-direction: column;
height: calc(
100% - 12px - 16px - 36px
); //12px -> attributes padding, 16px -> tabs margin bottom, 36px -> tabs height
overflow: hidden;
.no-data {
height: 400px;
@@ -18,6 +22,8 @@
flex-direction: column;
gap: 12px;
padding: 12px;
flex: 1;
overflow-y: auto;
.item {
display: flex;

View File

@@ -1,4 +1,11 @@
.events-table {
display: flex;
flex-direction: column;
height: calc(
100% - 12px - 16px - 36px
); //12px -> attributes padding, 16px -> tabs margin bottom, 36px -> tabs height
overflow: hidden;
.no-events {
display: flex;
justify-content: center;
@@ -10,6 +17,8 @@
flex-direction: column;
gap: 12px;
padding: 12px;
flex: 1;
overflow-y: auto;
.event {
.ant-collapse {

View File

@@ -10,6 +10,10 @@
flex-direction: column;
gap: 12px;
padding: 12px;
height: calc(
100% - 12px - 16px - 36px
); //12px -> attributes padding, 16px -> tabs margin bottom, 36px -> tabs height
overflow-y: auto;
.item {
display: flex;

View File

@@ -1,9 +1,9 @@
.span-details-drawer {
display: flex;
flex-direction: column;
width: 450px;
height: 100%;
border-left: 1px solid var(--bg-slate-400);
overflow-y: auto;
&::-webkit-scrollbar {
width: 0.1rem;
@@ -43,7 +43,7 @@
.description {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 16px;
padding: 10px 12px;
@@ -143,7 +143,29 @@
}
.attributes-events {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
.details-drawer-tabs {
display: flex;
flex-direction: column;
height: 100%;
.ant-tabs-content-holder {
flex: 1;
overflow: hidden;
.ant-tabs-content {
height: 100%;
.ant-tabs-tabpane {
height: 100%;
overflow: hidden;
}
}
}
.ant-tabs-extra-content {
display: flex;
align-items: center;
@@ -215,14 +237,6 @@
}
}
.span-details-drawer-docked {
width: 48px;
.header {
justify-content: center;
}
}
.lightMode {
.span-details-drawer {
border-left: 1px solid var(--bg-vanilla-300);

View File

@@ -1,7 +1,6 @@
import './SpanDetailsDrawer.styles.scss';
import { Button, Tabs, TabsProps, Tooltip, Typography } from 'antd';
import cx from 'classnames';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -10,8 +9,8 @@ import { getTraceToLogsQuery } from 'container/TraceDetail/SelectedSpanDetails/c
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
import { Anvil, Bookmark, Link2, PanelRight, Search } from 'lucide-react';
import { Dispatch, SetStateAction, useState } from 'react';
import { Anvil, Bookmark, Link2, Search } from 'lucide-react';
import { useState } from 'react';
import { Span } from 'types/api/trace/getTraceV2';
import { formatEpochTimestamp } from 'utils/timeUtils';
@@ -21,8 +20,6 @@ import LinkedSpans from './LinkedSpans/LinkedSpans';
const FIVE_MINUTES_IN_MS = 5 * 60 * 1000;
interface ISpanDetailsDrawerProps {
isSpanDetailsDocked: boolean;
setIsSpanDetailsDocked: Dispatch<SetStateAction<boolean>>;
selectedSpan: Span | undefined;
traceID: string;
traceStartTime: number;
@@ -30,14 +27,7 @@ interface ISpanDetailsDrawerProps {
}
function SpanDetailsDrawer(props: ISpanDetailsDrawerProps): JSX.Element {
const {
isSpanDetailsDocked,
setIsSpanDetailsDocked,
selectedSpan,
traceStartTime,
traceID,
traceEndTime,
} = props;
const { selectedSpan, traceStartTime, traceID, traceEndTime } = props;
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
const color = generateColor(
@@ -115,131 +105,119 @@ function SpanDetailsDrawer(props: ISpanDetailsDrawerProps): JSX.Element {
);
};
if (!selectedSpan) {
return <div />;
}
return (
<div
className={cx(
'span-details-drawer',
isSpanDetailsDocked ? 'span-details-drawer-docked' : '',
)}
>
<section className="header">
{!isSpanDetailsDocked && (
<div className="heading">
<div className="dot" style={{ background: color }} />
<Typography.Text className="text">Span Details</Typography.Text>
<div className="span-details-drawer">
<section className="description">
<div className="item">
<Typography.Text className="attribute-key">span name</Typography.Text>
<Tooltip title={selectedSpan.name}>
<div className="value-wrapper">
<Typography.Text className="attribute-value" ellipsis>
{selectedSpan.name}
</Typography.Text>
</div>
</Tooltip>
</div>
<div className="item">
<Typography.Text className="attribute-key">span id</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.spanId}
</Typography.Text>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">start time</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{formatEpochTimestamp(selectedSpan.timestamp)}
</Typography.Text>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">duration</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{getYAxisFormattedValue(`${selectedSpan.durationNano}`, 'ns')}
</Typography.Text>
</div>
</div>
{selectedSpan.serviceName && (
<div className="item">
<Typography.Text className="attribute-key">service</Typography.Text>
<div className="service">
<div className="dot" style={{ backgroundColor: color }} />
<div className="value-wrapper">
<Tooltip title={selectedSpan.serviceName}>
<Typography.Text className="service-value" ellipsis>
{selectedSpan.serviceName}
</Typography.Text>
</Tooltip>
</div>
</div>
</div>
)}
<PanelRight
size={14}
cursor="pointer"
onClick={(): void => setIsSpanDetailsDocked((prev) => !prev)}
{selectedSpan.spanKind && (
<div className="item">
<Typography.Text className="attribute-key">span kind</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.spanKind}
</Typography.Text>
</div>
</div>
)}
{selectedSpan.statusCodeString && (
<div className="item">
<Typography.Text className="attribute-key">
status code string
</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.statusCodeString}
</Typography.Text>
</div>
</div>
)}
{selectedSpan.statusMessage && (
<div className="item">
<Typography.Text className="attribute-key">
status message
</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.statusMessage}
</Typography.Text>
</div>
</div>
)}
</section>
<Button onClick={onLogsHandler} className="related-logs">
Go to related logs
</Button>
<section className="attributes-events">
<Tabs
items={getItems(selectedSpan, traceStartTime)}
addIcon
defaultActiveKey="attributes"
className="details-drawer-tabs"
tabBarExtraContent={
<Search
size={14}
className="search-icon"
cursor="pointer"
onClick={(): void => setIsSearchVisible((prev) => !prev)}
/>
}
/>
</section>
{selectedSpan && !isSpanDetailsDocked && (
<>
<section className="description">
<div className="item">
<Typography.Text className="attribute-key">span name</Typography.Text>
<Tooltip title={selectedSpan.name}>
<div className="value-wrapper">
<Typography.Text className="attribute-value" ellipsis>
{selectedSpan.name}
</Typography.Text>
</div>
</Tooltip>
</div>
<div className="item">
<Typography.Text className="attribute-key">span id</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.spanId}
</Typography.Text>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">start time</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{formatEpochTimestamp(selectedSpan.timestamp)}
</Typography.Text>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">duration</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{getYAxisFormattedValue(`${selectedSpan.durationNano}`, 'ns')}
</Typography.Text>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">service</Typography.Text>
<div className="service">
<div className="dot" style={{ backgroundColor: color }} />
<div className="value-wrapper">
<Tooltip title={selectedSpan.serviceName}>
<Typography.Text className="service-value" ellipsis>
{selectedSpan.serviceName}
</Typography.Text>
</Tooltip>
</div>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">span kind</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.spanKind}
</Typography.Text>
</div>
</div>
<div className="item">
<Typography.Text className="attribute-key">
status code string
</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.statusCodeString}
</Typography.Text>
</div>
</div>
{selectedSpan.statusMessage && (
<div className="item">
<Typography.Text className="attribute-key">
status message
</Typography.Text>
<div className="value-wrapper">
<Typography.Text className="attribute-value">
{selectedSpan.statusMessage}
</Typography.Text>
</div>
</div>
)}
</section>
<Button onClick={onLogsHandler} className="related-logs">
Go to related logs
</Button>
<section className="attributes-events">
<Tabs
items={getItems(selectedSpan, traceStartTime)}
addIcon
defaultActiveKey="attributes"
className="details-drawer-tabs"
tabBarExtraContent={
<Search
size={14}
className="search-icon"
cursor="pointer"
onClick={(): void => setIsSearchVisible((prev) => !prev)}
/>
}
/>
</section>
</>
)}
</div>
);
}

View File

@@ -117,8 +117,6 @@ const renderSpanDetailsDrawer = (span: Span = createMockSpan()): any => {
<MemoryRouter>
<Route>
<SpanDetailsDrawer
isSpanDetailsDocked={false}
setIsSpanDetailsDocked={jest.fn()}
selectedSpan={span}
traceID={span.traceId}
traceStartTime={span.timestamp}

View File

@@ -2,6 +2,7 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './Success.styles.scss';
import { DrawerWrapper } from '@signozhq/drawer';
import { ColumnDef, createColumnHelper } from '@tanstack/react-table';
import { Virtualizer } from '@tanstack/react-virtual';
import { Button, Tooltip, Typography } from 'antd';
@@ -9,6 +10,7 @@ import cx from 'classnames';
import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
import { TableV3 } from 'components/TableV3/TableV3';
import { themeColors } from 'constants/theme';
import SpanDetailsDrawer from 'container/SpanDetailsDrawer/SpanDetailsDrawer';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import AddSpanToFunnelModal from 'container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal';
import SpanLineActionButtons from 'container/TraceWaterfall/SpanLineActionButtons';
@@ -192,13 +194,15 @@ function SpanOverview({
export function SpanDuration({
span,
traceMetadata,
setSelectedSpan,
selectedSpan,
handleOpenSpanDrawer,
}: {
span: Span;
traceMetadata: ITraceMetadata;
selectedSpan: Span | undefined;
setSelectedSpan: Dispatch<SetStateAction<Span | undefined>>;
handleOpenSpanDrawer: (span: Span) => void;
}): JSX.Element {
const { time, timeUnitName } = convertTimeToRelevantUnit(
span.durationNano / 1e6,
@@ -257,7 +261,7 @@ export function SpanDuration({
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={(): void => {
setSelectedSpan(span);
handleOpenSpanDrawer(span);
if (span?.spanId) {
urlQuery.set('spanId', span?.spanId);
}
@@ -320,14 +324,15 @@ function getWaterfallColumns({
selectedSpan,
setSelectedSpan,
handleAddSpanToFunnel,
handleOpenSpanDrawer,
}: {
handleCollapseUncollapse: (id: string, collapse: boolean) => void;
uncollapsedNodes: string[];
traceMetadata: ITraceMetadata;
selectedSpan: Span | undefined;
setSelectedSpan: Dispatch<SetStateAction<Span | undefined>>;
handleAddSpanToFunnel: (span: Span) => void;
handleOpenSpanDrawer: (span: Span) => void;
}): ColumnDef<Span, any>[] {
const waterfallColumns: ColumnDef<Span, any>[] = [
columnDefHelper.display({
@@ -364,7 +369,7 @@ function getWaterfallColumns({
span={props.row.original}
traceMetadata={traceMetadata}
selectedSpan={selectedSpan}
setSelectedSpan={setSelectedSpan}
handleOpenSpanDrawer={handleOpenSpanDrawer}
/>
),
}),
@@ -428,6 +433,23 @@ function Success(props: ISuccessProps): JSX.Element {
setSelectedSpanToAddToFunnel(span);
}, []);
const drawerTriggerRef = useRef<HTMLDivElement>(null);
const handleOpenSpanDrawer = useCallback(
(span: Span): void => {
setSelectedSpan(span);
// Trigger the drawer click programmatically
setTimeout(() => {
if (drawerTriggerRef.current) {
const button = drawerTriggerRef.current.querySelector('button');
if (button) {
button.click();
}
}
}, 0);
},
[setSelectedSpan],
);
const columns = useMemo(
() =>
getWaterfallColumns({
@@ -437,6 +459,7 @@ function Success(props: ISuccessProps): JSX.Element {
selectedSpan,
setSelectedSpan,
handleAddSpanToFunnel,
handleOpenSpanDrawer,
}),
[
handleCollapseUncollapse,
@@ -445,6 +468,7 @@ function Success(props: ISuccessProps): JSX.Element {
selectedSpan,
setSelectedSpan,
handleAddSpanToFunnel,
handleOpenSpanDrawer,
],
);
@@ -523,6 +547,32 @@ function Success(props: ISuccessProps): JSX.Element {
onClose={(): void => setIsAddSpanToFunnelModalOpen(false)}
/>
)}
{selectedSpan && (
<div ref={drawerTriggerRef} style={{ display: 'none' }}>
<DrawerWrapper
key={`drawer-${selectedSpan.spanId}`}
trigger={<Button>Open Span Details</Button>}
header={{
title: 'Span details',
description: '',
}}
content={
<SpanDetailsDrawer
selectedSpan={selectedSpan}
traceID={traceMetadata.traceId}
traceStartTime={traceMetadata.startTime}
traceEndTime={traceMetadata.endTime}
/>
}
showOverlay={false}
allowOutsideClick={false}
className="span-drawer"
direction="right"
type="panel"
/>
</div>
)}
</div>
);
}

View File

@@ -59,7 +59,7 @@ jest.mock('uplot', () => {
});
describe('SpanDuration', () => {
const mockSetSelectedSpan = jest.fn();
const mockHandleOpenSpanDrawer = jest.fn();
const mockUrlQuerySet = jest.fn();
const mockSafeNavigate = jest.fn();
const mockUrlQueryGet = jest.fn();
@@ -86,7 +86,7 @@ describe('SpanDuration', () => {
span={mockSpan}
traceMetadata={mockTraceMetadata}
selectedSpan={undefined}
setSelectedSpan={mockSetSelectedSpan}
handleOpenSpanDrawer={mockHandleOpenSpanDrawer}
/>,
);
@@ -95,7 +95,7 @@ describe('SpanDuration', () => {
fireEvent.click(spanElement);
// Verify setSelectedSpan was called with the correct span
expect(mockSetSelectedSpan).toHaveBeenCalledWith(mockSpan);
expect(mockHandleOpenSpanDrawer).toHaveBeenCalledWith(mockSpan);
// Verify URL query was updated
expect(mockUrlQuerySet).toHaveBeenCalledWith('spanId', 'test-span-id');
@@ -112,7 +112,7 @@ describe('SpanDuration', () => {
span={mockSpan}
traceMetadata={mockTraceMetadata}
selectedSpan={undefined}
setSelectedSpan={mockSetSelectedSpan}
handleOpenSpanDrawer={mockHandleOpenSpanDrawer}
/>,
);
@@ -138,7 +138,7 @@ describe('SpanDuration', () => {
span={mockSpan}
traceMetadata={mockTraceMetadata}
selectedSpan={mockSpan}
setSelectedSpan={mockSetSelectedSpan}
handleOpenSpanDrawer={mockHandleOpenSpanDrawer}
/>,
);

View File

@@ -62,8 +62,8 @@
.trace-left-content {
display: flex;
flex-direction: column;
gap: 25px;
padding-top: 16px;
width: 100%;
.flamegraph-waterfall-toggle {
display: flex;

View File

@@ -3,7 +3,6 @@ import './TraceDetailV2.styles.scss';
import { Button, Tabs } from 'antd';
import FlamegraphImg from 'assets/TraceDetail/Flamegraph';
import TraceFlamegraph from 'container/PaginatedTraceFlamegraph/PaginatedTraceFlamegraph';
import SpanDetailsDrawer from 'container/SpanDetailsDrawer/SpanDetailsDrawer';
import TraceMetadata from 'container/TraceMetadata/TraceMetadata';
import TraceWaterfall, {
IInterestedSpan,
@@ -30,7 +29,6 @@ function TraceDetailsV2(): JSX.Element {
traceFlamegraphStatsWidth,
setTraceFlamegraphStatsWidth,
] = useState<number>(450);
const [isSpanDetailsDocked, setIsSpanDetailsDocked] = useState<boolean>(false);
const [selectedSpan, setSelectedSpan] = useState<Span>();
useEffect(() => {
@@ -58,12 +56,6 @@ function TraceDetailsV2(): JSX.Element {
}
}, [traceData]);
useEffect(() => {
if (selectedSpan) {
setIsSpanDetailsDocked(false);
}
}, [selectedSpan]);
const noData = useMemo(
() =>
!isFetchingTraceData &&
@@ -76,12 +68,6 @@ function TraceDetailsV2(): JSX.Element {
],
);
useEffect(() => {
if (noData) {
setIsSpanDetailsDocked(true);
}
}, [noData]);
const items = [
{
label: (
@@ -122,10 +108,7 @@ function TraceDetailsV2(): JSX.Element {
return (
<div className="trace-layout">
<div
className="trace-left-content"
style={{ width: `calc(100% - ${isSpanDetailsDocked ? 48 : 330}px)` }}
>
<div className="trace-left-content">
<TraceMetadata
traceID={traceId}
duration={
@@ -145,14 +128,6 @@ function TraceDetailsV2(): JSX.Element {
<NoData />
)}
</div>
<SpanDetailsDrawer
isSpanDetailsDocked={isSpanDetailsDocked}
setIsSpanDetailsDocked={setIsSpanDetailsDocked}
selectedSpan={selectedSpan}
traceID={traceId}
traceStartTime={traceData?.payload?.startTimestampMillis || 0}
traceEndTime={traceData?.payload?.endTimestampMillis || 0}
/>
</div>
);
}

View File

@@ -3603,6 +3603,26 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
"@radix-ui/react-dialog@^1.1.1", "@radix-ui/react-dialog@^1.1.11":
version "1.1.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz#1de3d7a7e9a17a9874d29c07f5940a18a119b632"
integrity sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==
dependencies:
"@radix-ui/primitive" "1.1.3"
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-context" "1.1.2"
"@radix-ui/react-dismissable-layer" "1.1.11"
"@radix-ui/react-focus-guards" "1.1.3"
"@radix-ui/react-focus-scope" "1.1.7"
"@radix-ui/react-id" "1.1.1"
"@radix-ui/react-portal" "1.1.9"
"@radix-ui/react-presence" "1.1.5"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-slot" "1.2.3"
"@radix-ui/react-use-controllable-state" "1.2.2"
aria-hidden "^1.2.4"
react-remove-scroll "^2.6.3"
"@radix-ui/react-direction@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
@@ -4281,6 +4301,21 @@
resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-1.1.4.tgz#5d5de5bd9d19b6a3631383db015cc4b70c3f7661"
integrity sha512-ICZz5szxTq8NcKAsk6LP+nSybPyEcyy8eu2zfxlPQCnJ1YjJP1PglaKLlF0N6+D60gAd3yC5he06BqR8/HxjNg==
"@signozhq/drawer@0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@signozhq/drawer/-/drawer-0.0.3.tgz#00b1bb4f354f68ff69ddf7a29519351a0839fac4"
integrity sha512-SMt4wyECAEvEzL3Xg7nXBWCG6mLRuPDLKP/gQlH4DiOAOKiSqaiwdxZudx/MXVjxtR8IHVP/xTIAK2Lrh+75BQ==
dependencies:
"@radix-ui/react-dialog" "^1.1.11"
"@radix-ui/react-icons" "^1.3.0"
"@radix-ui/react-slot" "^1.1.0"
class-variance-authority "^0.7.0"
clsx "^2.1.1"
lucide-react "^0.445.0"
tailwind-merge "^2.5.2"
tailwindcss-animate "^1.0.7"
vaul "^1.1.2"
"@signozhq/input@0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@signozhq/input/-/input-0.0.2.tgz#b2fea8c0979a53984ebcd5e3c3c50b38082eb1b1"
@@ -17976,6 +18011,13 @@ vary@~1.1.2:
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
vaul@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vaul/-/vaul-1.1.2.tgz#c959f8b9dc2ed4f7d99366caee433fbef91f5ba9"
integrity sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==
dependencies:
"@radix-ui/react-dialog" "^1.1.1"
vfile-location@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-4.1.0.tgz#69df82fb9ef0a38d0d02b90dd84620e120050dd0"