@rozenite/performance-monitor-plugin 1.0.0-alpha.11

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.
Files changed (58) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +130 -0
  3. package/dist/App.html +31 -0
  4. package/dist/assets/App-C1ubeKf9.js +119 -0
  5. package/dist/assets/App-DRvEE1L4.css +1 -0
  6. package/dist/react-native.cjs +1 -0
  7. package/dist/react-native.d.ts +1 -0
  8. package/dist/react-native.js +5 -0
  9. package/dist/rozenite.config.d.ts +7 -0
  10. package/dist/rozenite.json +1 -0
  11. package/dist/src/react-native/asserts.d.ts +4 -0
  12. package/dist/src/react-native/helpers.d.ts +1 -0
  13. package/dist/src/react-native/performance-monitor.d.ts +8 -0
  14. package/dist/src/react-native/usePerformanceMonitorDevTools.d.ts +1 -0
  15. package/dist/src/shared/types.d.ts +39 -0
  16. package/dist/src/ui/App.d.ts +1 -0
  17. package/dist/src/ui/components/DataTable.d.ts +8 -0
  18. package/dist/src/ui/components/DetailsDisplay.d.ts +4 -0
  19. package/dist/src/ui/components/DetailsSidebar.d.ts +6 -0
  20. package/dist/src/ui/components/ExportModal.d.ts +9 -0
  21. package/dist/src/ui/components/JsonTree.d.ts +4 -0
  22. package/dist/src/ui/components/MarkDetails.d.ts +5 -0
  23. package/dist/src/ui/components/MarksTable.d.ts +6 -0
  24. package/dist/src/ui/components/MeasureDetails.d.ts +5 -0
  25. package/dist/src/ui/components/MeasuresTable.d.ts +6 -0
  26. package/dist/src/ui/components/MetricDetails.d.ts +5 -0
  27. package/dist/src/ui/components/MetricsTable.d.ts +6 -0
  28. package/dist/src/ui/components/SessionDuration.d.ts +5 -0
  29. package/dist/src/ui/utils.d.ts +2 -0
  30. package/dist/usePerformanceMonitorDevTools.cjs +1 -0
  31. package/dist/usePerformanceMonitorDevTools.js +118 -0
  32. package/package.json +41 -0
  33. package/project.json +12 -0
  34. package/react-native.ts +8 -0
  35. package/rozenite.config.ts +8 -0
  36. package/src/react-native/asserts.ts +29 -0
  37. package/src/react-native/helpers.ts +4 -0
  38. package/src/react-native/performance-monitor.ts +171 -0
  39. package/src/react-native/usePerformanceMonitorDevTools.ts +31 -0
  40. package/src/shared/types.ts +50 -0
  41. package/src/ui/App.css +97 -0
  42. package/src/ui/App.tsx +286 -0
  43. package/src/ui/components/DataTable.tsx +117 -0
  44. package/src/ui/components/DetailsDisplay.tsx +26 -0
  45. package/src/ui/components/DetailsSidebar.tsx +87 -0
  46. package/src/ui/components/ExportModal.tsx +278 -0
  47. package/src/ui/components/JsonTree.tsx +35 -0
  48. package/src/ui/components/MarkDetails.tsx +45 -0
  49. package/src/ui/components/MarksTable.tsx +42 -0
  50. package/src/ui/components/MeasureDetails.tsx +74 -0
  51. package/src/ui/components/MeasuresTable.tsx +72 -0
  52. package/src/ui/components/MetricDetails.tsx +56 -0
  53. package/src/ui/components/MetricsTable.tsx +54 -0
  54. package/src/ui/components/SessionDuration.tsx +54 -0
  55. package/src/ui/utils.ts +17 -0
  56. package/tsconfig.json +32 -0
  57. package/tsconfig.tsbuildinfo +1 -0
  58. package/vite.config.ts +20 -0
@@ -0,0 +1,50 @@
1
+ import type { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
2
+
3
+ export type SharedPerformanceEntryProperties = {
4
+ name: string;
5
+ startTime: number;
6
+ duration: number;
7
+ };
8
+
9
+ export type SerializedPerformanceMeasure = SharedPerformanceEntryProperties & {
10
+ entryType: 'measure';
11
+ detail?: unknown;
12
+ };
13
+
14
+ export type SerializedPerformanceMark = SharedPerformanceEntryProperties & {
15
+ entryType: 'mark';
16
+ detail?: unknown;
17
+ };
18
+
19
+ export type SerializedPerformanceMetric = SharedPerformanceEntryProperties & {
20
+ entryType: 'metric';
21
+ value: string | number;
22
+ detail?: unknown;
23
+ };
24
+
25
+ export type SerializedPerformanceEntry =
26
+ | SerializedPerformanceMeasure
27
+ | SerializedPerformanceMark
28
+ | SerializedPerformanceMetric;
29
+
30
+ export type PerformanceMonitorEventMap = {
31
+ setEnabled: {
32
+ enabled: boolean;
33
+ };
34
+ setSession: {
35
+ sessionStartedAt: number;
36
+ timeOrigin: number;
37
+ };
38
+ appendMeasures: {
39
+ measures: SerializedPerformanceMeasure[];
40
+ };
41
+ appendMarks: {
42
+ marks: SerializedPerformanceMark[];
43
+ };
44
+ setMetrics: {
45
+ metrics: SerializedPerformanceMetric[];
46
+ };
47
+ };
48
+
49
+ export type PerformanceMonitorDevToolsClient =
50
+ RozeniteDevToolsClient<PerformanceMonitorEventMap>;
package/src/ui/App.css ADDED
@@ -0,0 +1,97 @@
1
+ /* Custom scrollbar for dark theme */
2
+ ::-webkit-scrollbar {
3
+ width: 8px;
4
+ height: 8px;
5
+ }
6
+
7
+ ::-webkit-scrollbar-track {
8
+ background: #1a1a1a;
9
+ }
10
+
11
+ ::-webkit-scrollbar-thumb {
12
+ background: #333333;
13
+ border-radius: 4px;
14
+ }
15
+
16
+ ::-webkit-scrollbar-thumb:hover {
17
+ background: #a0a0a0;
18
+ }
19
+
20
+ .radix-themes {
21
+ --cursor-button: pointer;
22
+ --cursor-checkbox: pointer;
23
+ --cursor-link: pointer;
24
+ --cursor-menu-item: pointer;
25
+ --cursor-radio: pointer;
26
+ --cursor-slider-thumb: grab;
27
+ --cursor-slider-thumb-active: grabbing;
28
+ --cursor-switch: pointer;
29
+ }
30
+
31
+ .table-row-hover:hover {
32
+ background-color: hsl(0 0% 16%) !important;
33
+ }
34
+
35
+ /* Data Table Styles */
36
+ .data-table-container {
37
+ width: 100%;
38
+ height: 100%;
39
+ overflow: hidden;
40
+ }
41
+
42
+ .data-table {
43
+ width: 100%;
44
+ border-collapse: collapse;
45
+ font-size: 12px;
46
+ background: hsl(0 0% 3.9%);
47
+ color: hsl(0 0% 98%);
48
+ }
49
+
50
+ .data-table-header-row {
51
+ background: hsl(0 0% 3.9%);
52
+ border-bottom: 1px solid hsl(0 0% 14.9%);
53
+ position: sticky;
54
+ top: 0;
55
+ z-index: 10;
56
+ }
57
+
58
+ .data-table-header-cell {
59
+ padding: 8px 12px;
60
+ text-align: left;
61
+ font-weight: 600;
62
+ color: hsl(0 0% 98%);
63
+ font-size: 11px;
64
+ text-transform: uppercase;
65
+ letter-spacing: 0.05em;
66
+ border-bottom: 1px solid hsl(0 0% 14.9%);
67
+ transition: background-color 0.15s ease;
68
+ }
69
+
70
+ .data-table-header-cell:hover {
71
+ background-color: hsl(0 0% 16%);
72
+ }
73
+
74
+ .data-table-row {
75
+ border-bottom: 1px solid hsl(0 0% 14.9%);
76
+ transition: background-color 0.15s ease;
77
+ }
78
+
79
+ .data-table-row:hover {
80
+ background-color: hsl(0 0% 16%);
81
+ }
82
+
83
+ .data-table-cell {
84
+ padding: 8px 12px;
85
+ vertical-align: top;
86
+ border-bottom: 1px solid hsl(0 0% 14.9%);
87
+ color: hsl(0 0% 98%);
88
+ }
89
+
90
+ .index-cell,
91
+ .index-header {
92
+ width: 40px;
93
+ text-align: center;
94
+ font-weight: 600;
95
+ color: hsl(0 0% 63.9%);
96
+ font-size: 11px;
97
+ }
package/src/ui/App.tsx ADDED
@@ -0,0 +1,286 @@
1
+ import {
2
+ useRozeniteDevToolsClient,
3
+ Subscription,
4
+ } from '@rozenite/plugin-bridge';
5
+ import {
6
+ PerformanceMonitorEventMap,
7
+ SerializedPerformanceMeasure,
8
+ SerializedPerformanceMark,
9
+ SerializedPerformanceMetric,
10
+ SerializedPerformanceEntry,
11
+ } from '../shared/types';
12
+ import { useEffect, useState } from 'react';
13
+ import {
14
+ Theme,
15
+ Tabs,
16
+ Button,
17
+ Heading,
18
+ Text,
19
+ Flex,
20
+ Box,
21
+ } from '@radix-ui/themes';
22
+ import '@radix-ui/themes/styles.css';
23
+ import './App.css';
24
+ import { MeasuresTable } from './components/MeasuresTable';
25
+ import { MetricsTable } from './components/MetricsTable';
26
+ import { MarksTable } from './components/MarksTable';
27
+ import { DetailsSidebar } from './components/DetailsSidebar';
28
+ import { SessionDuration } from './components/SessionDuration';
29
+ import { ExportModal } from './components/ExportModal';
30
+
31
+ type PerformanceMonitorSession = {
32
+ sessionStartedAt: number;
33
+ clockShift: number;
34
+ measures: SerializedPerformanceMeasure[];
35
+ marks: SerializedPerformanceMark[];
36
+ metrics: SerializedPerformanceMetric[];
37
+ };
38
+
39
+ export default function PerformanceMonitorPanel() {
40
+ const client = useRozeniteDevToolsClient<PerformanceMonitorEventMap>({
41
+ pluginId: '@rozenite/performance-monitor-plugin',
42
+ });
43
+ const [session, setSession] = useState<PerformanceMonitorSession>({
44
+ sessionStartedAt: 0,
45
+ clockShift: 0,
46
+ measures: [],
47
+ marks: [],
48
+ metrics: [],
49
+ });
50
+ const [isSessionActive, setIsSessionActive] = useState(false);
51
+ const [selectedItem, setSelectedItem] =
52
+ useState<SerializedPerformanceEntry | null>(null);
53
+
54
+ useEffect(() => {
55
+ if (!client) {
56
+ return;
57
+ }
58
+
59
+ const subscriptions: Subscription[] = [];
60
+
61
+ subscriptions.push(
62
+ client.onMessage('setSession', ({ sessionStartedAt }) => {
63
+ const receivedAt = Date.now();
64
+ setSession({
65
+ sessionStartedAt: receivedAt,
66
+ // It's likely that there is a small clock shift between the device and the DevTools.
67
+ clockShift: receivedAt - sessionStartedAt,
68
+ measures: [],
69
+ marks: [],
70
+ metrics: [],
71
+ });
72
+ setIsSessionActive(true);
73
+ })
74
+ );
75
+
76
+ subscriptions.push(
77
+ client.onMessage('appendMeasures', ({ measures }) => {
78
+ setSession((oldSession) => ({
79
+ ...oldSession,
80
+ measures: [
81
+ ...oldSession.measures,
82
+ ...measures.map((measure) => ({
83
+ ...measure,
84
+ startTime: measure.startTime + oldSession.clockShift,
85
+ })),
86
+ ],
87
+ }));
88
+ })
89
+ );
90
+
91
+ subscriptions.push(
92
+ client.onMessage('appendMarks', ({ marks }) => {
93
+ setSession((oldSession) => ({
94
+ ...oldSession,
95
+ marks: [
96
+ ...oldSession.marks,
97
+ ...marks.map((mark) => ({
98
+ ...mark,
99
+ startTime: mark.startTime + oldSession.clockShift,
100
+ })),
101
+ ],
102
+ }));
103
+ })
104
+ );
105
+
106
+ subscriptions.push(
107
+ client.onMessage('setMetrics', ({ metrics }) => {
108
+ setSession((oldSession) => ({
109
+ ...oldSession,
110
+ metrics: [
111
+ ...oldSession.metrics,
112
+ ...metrics.map((metric) => ({
113
+ ...metric,
114
+ startTime: metric.startTime + oldSession.clockShift,
115
+ })),
116
+ ],
117
+ }));
118
+ })
119
+ );
120
+
121
+ return () => {
122
+ subscriptions.forEach((subscription) => subscription.remove());
123
+ client.send('setEnabled', { enabled: false });
124
+ };
125
+ }, [client]);
126
+
127
+ const handleStartSession = () => {
128
+ if (client && !isSessionActive) {
129
+ client.send('setEnabled', { enabled: true });
130
+ setIsSessionActive(true);
131
+ }
132
+ };
133
+
134
+ const handleStopSession = () => {
135
+ if (client && isSessionActive) {
136
+ client.send('setEnabled', { enabled: false });
137
+ setIsSessionActive(false);
138
+ }
139
+ };
140
+
141
+ const handleEntryClick = (entry: SerializedPerformanceEntry) => {
142
+ setSelectedItem(entry);
143
+ };
144
+
145
+ const handleCloseSidebar = () => {
146
+ setSelectedItem(null);
147
+ };
148
+
149
+ return (
150
+ <Theme appearance="dark" accentColor="blue" radius="medium">
151
+ <Box
152
+ p="4"
153
+ height="100vh"
154
+ style={{ display: 'flex', flexDirection: 'column' }}
155
+ >
156
+ {/* Header */}
157
+ <Box mb="4" style={{ flexShrink: 0 }}>
158
+ <Heading size="6" mb="2">
159
+ Performance Monitor
160
+ </Heading>
161
+ <Flex gap="4" align="center">
162
+ <SessionDuration
163
+ isActive={isSessionActive}
164
+ sessionStartedAt={session.sessionStartedAt}
165
+ />
166
+ </Flex>
167
+ </Box>
168
+
169
+ {/* Toolbar */}
170
+ <Flex gap="3" align="center" mb="4" style={{ flexShrink: 0 }}>
171
+ <Button
172
+ onClick={handleStartSession}
173
+ disabled={isSessionActive}
174
+ color="green"
175
+ >
176
+ Start Session
177
+ </Button>
178
+ <Button
179
+ onClick={handleStopSession}
180
+ disabled={!isSessionActive}
181
+ color="red"
182
+ >
183
+ Stop Session
184
+ </Button>
185
+ <ExportModal
186
+ measures={session.measures}
187
+ metrics={session.metrics}
188
+ marks={session.marks}
189
+ sessionStartedAt={session.sessionStartedAt}
190
+ clockShift={session.clockShift}
191
+ />
192
+ <Flex gap="2" align="center" ml="auto">
193
+ <Box
194
+ style={{
195
+ width: '8px',
196
+ height: '8px',
197
+ borderRadius: '50%',
198
+ backgroundColor: isSessionActive ? '#10b981' : '#ef4444',
199
+ }}
200
+ />
201
+ <Text size="2" color="gray">
202
+ {isSessionActive ? 'Session Active' : 'Session Inactive'}
203
+ </Text>
204
+ </Flex>
205
+ </Flex>
206
+
207
+ {/* Tabs */}
208
+ <Box
209
+ style={{
210
+ flex: '1',
211
+ display: 'flex',
212
+ flexDirection: 'column',
213
+ minHeight: 0,
214
+ }}
215
+ >
216
+ <Tabs.Root
217
+ defaultValue="measures"
218
+ style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
219
+ >
220
+ <Tabs.List style={{ flexShrink: 0 }}>
221
+ <Tabs.Trigger value="measures">
222
+ Measures ({session.measures.length})
223
+ </Tabs.Trigger>
224
+ <Tabs.Trigger value="metrics">
225
+ Metrics ({session.metrics.length})
226
+ </Tabs.Trigger>
227
+ <Tabs.Trigger value="marks">
228
+ Marks ({session.marks.length})
229
+ </Tabs.Trigger>
230
+ </Tabs.List>
231
+
232
+ <Box
233
+ style={{
234
+ flexGrow: '1',
235
+ display: 'flex',
236
+ flexDirection: 'column',
237
+ minHeight: 0,
238
+ }}
239
+ >
240
+ <Tabs.Content
241
+ value="measures"
242
+ style={{
243
+ display: 'contents',
244
+ }}
245
+ >
246
+ <MeasuresTable
247
+ measures={session.measures}
248
+ onRowClick={handleEntryClick}
249
+ />
250
+ </Tabs.Content>
251
+
252
+ <Tabs.Content
253
+ value="metrics"
254
+ style={{
255
+ display: 'contents',
256
+ }}
257
+ >
258
+ <MetricsTable
259
+ metrics={session.metrics}
260
+ onRowClick={handleEntryClick}
261
+ />
262
+ </Tabs.Content>
263
+
264
+ <Tabs.Content
265
+ value="marks"
266
+ style={{
267
+ display: 'contents',
268
+ }}
269
+ >
270
+ <MarksTable
271
+ marks={session.marks}
272
+ onRowClick={handleEntryClick}
273
+ />
274
+ </Tabs.Content>
275
+ </Box>
276
+ </Tabs.Root>
277
+ </Box>
278
+ </Box>
279
+
280
+ <DetailsSidebar
281
+ selectedItem={selectedItem}
282
+ onClose={handleCloseSidebar}
283
+ />
284
+ </Theme>
285
+ );
286
+ }
@@ -0,0 +1,117 @@
1
+ import {
2
+ useReactTable,
3
+ getCoreRowModel,
4
+ flexRender,
5
+ getSortedRowModel,
6
+ SortingState,
7
+ ColumnDef,
8
+ } from '@tanstack/react-table';
9
+ import { TableVirtuoso } from 'react-virtuoso';
10
+ import { Text, Flex, Box } from '@radix-ui/themes';
11
+ import { useState } from 'react';
12
+
13
+ export type DataTableProps<TData> = {
14
+ data: TData[];
15
+ columns: ColumnDef<TData>[];
16
+ onRowClick?: (item: TData) => void;
17
+ emptyMessage?: string;
18
+ };
19
+
20
+ export const DataTable = <TData,>({
21
+ data,
22
+ columns,
23
+ onRowClick,
24
+ emptyMessage = 'No data available',
25
+ }: DataTableProps<TData>) => {
26
+ const [sorting, setSorting] = useState<SortingState>([]);
27
+
28
+ const table = useReactTable({
29
+ data,
30
+ columns,
31
+ state: {
32
+ sorting,
33
+ },
34
+ onSortingChange: setSorting,
35
+ getCoreRowModel: getCoreRowModel(),
36
+ getSortedRowModel: getSortedRowModel(),
37
+ });
38
+
39
+ const { rows } = table.getRowModel();
40
+
41
+ if (data.length === 0) {
42
+ return (
43
+ <Box pt="3" pl="3">
44
+ <Text size="2" color="gray">
45
+ {emptyMessage}
46
+ </Text>
47
+ </Box>
48
+ );
49
+ }
50
+
51
+ return (
52
+ <div className="data-table-container">
53
+ <TableVirtuoso
54
+ style={{ height: '100%' }}
55
+ totalCount={rows.length}
56
+ components={{
57
+ Table: (props) => <table {...props} className="data-table" />,
58
+ TableRow: (props) => {
59
+ const index = props['data-index'];
60
+ const row = rows[index];
61
+
62
+ return (
63
+ <tr
64
+ {...props}
65
+ onClick={() => onRowClick?.(row.original)}
66
+ style={{
67
+ cursor: onRowClick ? 'pointer' : 'default',
68
+ transition: 'background-color 0.15s ease',
69
+ }}
70
+ className="data-table-row"
71
+ >
72
+ <td className="data-table-cell index-cell">{index + 1}</td>
73
+ {row.getVisibleCells().map((cell) => (
74
+ <td key={cell.id} className="data-table-cell">
75
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
76
+ </td>
77
+ ))}
78
+ </tr>
79
+ );
80
+ },
81
+ }}
82
+ fixedHeaderContent={() => {
83
+ return table.getHeaderGroups().map((headerGroup) => (
84
+ <tr key={headerGroup.id} className="data-table-header-row">
85
+ <th className="data-table-header-cell index-header">#</th>
86
+ {headerGroup.headers.map((header) => (
87
+ <th
88
+ key={header.id}
89
+ className="data-table-header-cell"
90
+ onClick={header.column.getToggleSortingHandler()}
91
+ style={{
92
+ cursor: header.column.getCanSort() ? 'pointer' : 'default',
93
+ }}
94
+ >
95
+ <Flex align="center" gap="2">
96
+ {flexRender(
97
+ header.column.columnDef.header,
98
+ header.getContext()
99
+ )}
100
+ {header.column.getCanSort() && (
101
+ <Text size="1" color="gray">
102
+ {{
103
+ asc: '⬆️',
104
+ desc: '⬇️',
105
+ }[header.column.getIsSorted() as string] ?? '↕️'}
106
+ </Text>
107
+ )}
108
+ </Flex>
109
+ </th>
110
+ ))}
111
+ </tr>
112
+ ));
113
+ }}
114
+ />
115
+ </div>
116
+ );
117
+ };
@@ -0,0 +1,26 @@
1
+ import { Box, Text, Heading } from '@radix-ui/themes';
2
+ import { ReactNode } from 'react';
3
+ import { JsonTree } from './JsonTree';
4
+
5
+ export type DetailsDisplayProps = {
6
+ details?: unknown;
7
+ };
8
+
9
+ export const DetailsDisplay = ({ details }: DetailsDisplayProps) => {
10
+ const renderValue = (value: unknown): ReactNode => {
11
+ if (value == null) {
12
+ return <Text color="gray">No details provided</Text>;
13
+ }
14
+
15
+ return <JsonTree data={details} />;
16
+ };
17
+
18
+ return (
19
+ <Box>
20
+ <Heading size="3" mb="3">
21
+ Details
22
+ </Heading>
23
+ <Box>{renderValue(details)}</Box>
24
+ </Box>
25
+ );
26
+ };
@@ -0,0 +1,87 @@
1
+ import { ScrollArea, Box, Button, Flex } from '@radix-ui/themes';
2
+ import { Cross2Icon } from '@radix-ui/react-icons';
3
+ import { MeasureDetails } from './MeasureDetails';
4
+ import { MetricDetails } from './MetricDetails';
5
+ import { MarkDetails } from './MarkDetails';
6
+ import { SerializedPerformanceEntry } from '../../shared/types';
7
+
8
+ export type DetailsSidebarProps = {
9
+ selectedItem: SerializedPerformanceEntry | null;
10
+ onClose: () => void;
11
+ };
12
+
13
+ export const DetailsSidebar = ({
14
+ selectedItem,
15
+ onClose,
16
+ }: DetailsSidebarProps) => {
17
+ const renderDetails = () => {
18
+ if (!selectedItem) return null;
19
+
20
+ switch (selectedItem.entryType) {
21
+ case 'measure':
22
+ return <MeasureDetails measure={selectedItem} />;
23
+ case 'metric':
24
+ return <MetricDetails metric={selectedItem} />;
25
+ case 'mark':
26
+ return <MarkDetails mark={selectedItem} />;
27
+ default:
28
+ return null;
29
+ }
30
+ };
31
+
32
+ if (!selectedItem) {
33
+ return null;
34
+ }
35
+
36
+ return (
37
+ <>
38
+ {/* Backdrop */}
39
+ <Box
40
+ position="fixed"
41
+ top="0"
42
+ left="0"
43
+ right="0"
44
+ bottom="0"
45
+ style={{
46
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
47
+ zIndex: 999,
48
+ }}
49
+ onClick={onClose}
50
+ />
51
+
52
+ {/* Sidebar */}
53
+ <Box
54
+ position="fixed"
55
+ top="0"
56
+ right="0"
57
+ minWidth="400px"
58
+ maxWidth="50vw"
59
+ height="100vh"
60
+ style={{
61
+ backgroundColor: '#1a1a1a',
62
+ borderLeft: '1px solid #333333',
63
+ zIndex: 1000,
64
+ boxShadow: '-4px 0 8px rgba(0, 0, 0, 0.3)',
65
+ }}
66
+ >
67
+ <Flex p="4" direction="column" height="100vh">
68
+ <Flex justify="between" align="center" mb="4">
69
+ <Box />
70
+ <Button
71
+ variant="ghost"
72
+ size="2"
73
+ onClick={onClose}
74
+ style={{ padding: '4px' }}
75
+ >
76
+ <Cross2Icon width="16" height="16" />
77
+ </Button>
78
+ </Flex>
79
+
80
+ <ScrollArea style={{ flex: 1 }}>
81
+ <Box pr="4">{renderDetails()}</Box>
82
+ </ScrollArea>
83
+ </Flex>
84
+ </Box>
85
+ </>
86
+ );
87
+ };