@rozenite/network-activity-plugin 1.0.0-alpha.10-canary.1754923715388 → 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.
package/dist/App.html CHANGED
@@ -22,8 +22,8 @@
22
22
  <script>
23
23
  var __ROZENITE_PANEL__ = true;
24
24
  </script>
25
- <script type="module" crossorigin src="./assets/App-BE5UWKca.js"></script>
26
- <link rel="stylesheet" crossorigin href="./assets/App-zkbklFIE.css">
25
+ <script type="module" crossorigin src="./assets/App-BKBLGSeM.js"></script>
26
+ <link rel="stylesheet" crossorigin href="./assets/App-Ct73Yrm6.css">
27
27
  </head>
28
28
  <body>
29
29
  <div id="root"></div>
@@ -20556,6 +20556,7 @@ const ScrollArea = reactExports.forwardRef(({ className, children, ...props }, r
20556
20556
  children: [
20557
20557
  /* @__PURE__ */ jsxRuntimeExports.jsx(Viewport, { className: "h-full w-full rounded-[inherit]", children }),
20558
20558
  /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollBar, {}),
20559
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollBar, { orientation: "horizontal" }),
20559
20560
  /* @__PURE__ */ jsxRuntimeExports.jsx(Corner, {})
20560
20561
  ]
20561
20562
  }
@@ -20577,6 +20578,38 @@ const ScrollBar = reactExports.forwardRef(({ className, orientation = "vertical"
20577
20578
  }
20578
20579
  ));
20579
20580
  ScrollBar.displayName = ScrollAreaScrollbar.displayName;
20581
+ const Section = ({ title, children }) => {
20582
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
20583
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-medium text-gray-300 mb-2", children: title }),
20584
+ children
20585
+ ] });
20586
+ };
20587
+ const KeyValueGrid = ({
20588
+ items = [],
20589
+ emptyMessage,
20590
+ className
20591
+ }) => {
20592
+ const gridClassName = cn(
20593
+ "grid grid-cols-[minmax(7rem,25%)_minmax(3rem,1fr)] gap-x-2 gap-y-2 text-sm",
20594
+ className
20595
+ );
20596
+ if (items.length === 0) {
20597
+ return emptyMessage ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: gridClassName, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "col-span-2 text-gray-500 italic", children: emptyMessage }) }) : null;
20598
+ }
20599
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: gridClassName, children: items.map((item, index2) => /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Fragment, { children: [
20600
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
20601
+ "span",
20602
+ {
20603
+ className: cn(
20604
+ "text-gray-400 wrap-anywhere",
20605
+ item.keyClassName
20606
+ ),
20607
+ children: item.key
20608
+ }
20609
+ ),
20610
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("wrap-anywhere", item.valueClassName), children: item.value })
20611
+ ] }, index2)) });
20612
+ };
20580
20613
  async function copyToClipboard(text2) {
20581
20614
  return navigator.clipboard.writeText(text2);
20582
20615
  }
@@ -20693,78 +20726,74 @@ const CopyAsCurlButton = ({
20693
20726
  );
20694
20727
  };
20695
20728
  const HeadersTab = ({ selectedRequest }) => {
20696
- var _a, _b, _c;
20697
- const url = reactExports.useMemo(() => {
20698
- const { hostname, port, pathname } = new URL(selectedRequest.request.url);
20699
- return `${hostname}${port ? `:${port}` : ""}${pathname}`;
20700
- }, [selectedRequest.request.url]);
20701
- const isCopyAsCurlEnabled = ((_a = selectedRequest.request.body) == null ? void 0 : _a.data.type) !== "binary";
20729
+ var _a, _b;
20730
+ const requestBody = selectedRequest.request.body;
20731
+ const generalItems = reactExports.useMemo(
20732
+ () => {
20733
+ var _a2, _b2;
20734
+ return [
20735
+ {
20736
+ key: "Request URL",
20737
+ value: selectedRequest.request.url,
20738
+ valueClassName: "text-blue-400"
20739
+ },
20740
+ {
20741
+ key: "Request Method",
20742
+ value: selectedRequest.request.method
20743
+ },
20744
+ {
20745
+ key: "Status Code",
20746
+ value: ((_a2 = selectedRequest.response) == null ? void 0 : _a2.status) ?? "Pending",
20747
+ valueClassName: getStatusColor(((_b2 = selectedRequest.response) == null ? void 0 : _b2.status) ?? 0)
20748
+ },
20749
+ ...requestBody ? [
20750
+ {
20751
+ key: "Content-Type",
20752
+ value: requestBody.type,
20753
+ valueClassName: "text-blue-400"
20754
+ }
20755
+ ] : []
20756
+ ];
20757
+ },
20758
+ [selectedRequest]
20759
+ );
20760
+ const responseHeadersItems = reactExports.useMemo(() => {
20761
+ var _a2;
20762
+ const headers = (_a2 = selectedRequest.response) == null ? void 0 : _a2.headers;
20763
+ if (!headers) return [];
20764
+ return Object.entries(headers).map(([key, value]) => ({
20765
+ key: key.toLowerCase(),
20766
+ value
20767
+ }));
20768
+ }, [(_a = selectedRequest.response) == null ? void 0 : _a.headers]);
20769
+ const requestHeadersItems = reactExports.useMemo(() => {
20770
+ const headers = selectedRequest.request.headers;
20771
+ if (!headers) return [];
20772
+ return Object.entries(headers).map(([key, value]) => ({
20773
+ key: key.toLowerCase(),
20774
+ value
20775
+ }));
20776
+ }, [selectedRequest.request.headers]);
20777
+ const isCopyAsCurlEnabled = ((_b = selectedRequest.request.body) == null ? void 0 : _b.data.type) !== "binary";
20702
20778
  return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "h-full w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 space-y-4", children: [
20703
20779
  isCopyAsCurlEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(CopyAsCurlButton, { selectedRequest }),
20704
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
20705
- /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-medium text-gray-300 mb-2", children: "General" }),
20706
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1 text-sm", children: [
20707
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
20708
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 text-gray-400", children: "Request URL:" }),
20709
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: url })
20710
- ] }),
20711
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
20712
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 text-gray-400", children: "Request Method:" }),
20713
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: selectedRequest.request.method })
20714
- ] }),
20715
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
20716
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 text-gray-400", children: "Status Code:" }),
20717
- /* @__PURE__ */ jsxRuntimeExports.jsx(
20718
- "span",
20719
- {
20720
- className: getStatusColor(
20721
- ((_b = selectedRequest.response) == null ? void 0 : _b.status) ?? 0
20722
- ),
20723
- children: ((_c = selectedRequest.response) == null ? void 0 : _c.status) ?? "Pending"
20724
- }
20725
- )
20726
- ] }),
20727
- selectedRequest.request.body && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
20728
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 text-gray-400", children: "Content-Type:" }),
20729
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: selectedRequest.request.body.type })
20730
- ] })
20731
- ] })
20732
- ] }),
20733
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
20734
- /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-medium text-gray-300 mb-2", children: "Response Headers" }),
20735
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 text-sm font-mono", children: (() => {
20736
- var _a2;
20737
- const responseHeaders = (_a2 = selectedRequest.response) == null ? void 0 : _a2.headers;
20738
- if (responseHeaders && Object.keys(responseHeaders).length > 0) {
20739
- return Object.entries(responseHeaders).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
20740
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-32 text-gray-400", children: [
20741
- key.toLowerCase(),
20742
- ":"
20743
- ] }),
20744
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 break-all", children: value })
20745
- ] }, key));
20746
- } else {
20747
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-gray-500 italic", children: "No response headers available" });
20748
- }
20749
- })() })
20750
- ] }),
20751
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
20752
- /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-medium text-gray-300 mb-2", children: "Request Headers" }),
20753
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 text-sm font-mono", children: (() => {
20754
- const requestHeaders = selectedRequest.request.headers;
20755
- if (requestHeaders && Object.keys(requestHeaders).length > 0) {
20756
- return Object.entries(requestHeaders).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
20757
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-32 text-gray-400", children: [
20758
- key.toLowerCase(),
20759
- ":"
20760
- ] }),
20761
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 break-all", children: value })
20762
- ] }, key));
20763
- } else {
20764
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-gray-500 italic", children: "No request headers available" });
20765
- }
20766
- })() })
20767
- ] })
20780
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { title: "General", children: /* @__PURE__ */ jsxRuntimeExports.jsx(KeyValueGrid, { items: generalItems }) }),
20781
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { title: "Response Headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
20782
+ KeyValueGrid,
20783
+ {
20784
+ items: responseHeadersItems,
20785
+ emptyMessage: "No response headers available",
20786
+ className: "font-mono"
20787
+ }
20788
+ ) }),
20789
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { title: "Request Headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
20790
+ KeyValueGrid,
20791
+ {
20792
+ items: requestHeadersItems,
20793
+ emptyMessage: "No request headers available",
20794
+ className: "font-mono"
20795
+ }
20796
+ ) })
20768
20797
  ] }) });
20769
20798
  };
20770
20799
  var colorString = { exports: {} };
@@ -25461,21 +25490,23 @@ const SidePanel = () => {
25461
25490
  };
25462
25491
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-1/2 flex flex-col bg-gray-900", children: [
25463
25492
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-3 border-b border-gray-700 bg-gray-800", children: [
25464
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
25493
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [
25465
25494
  /* @__PURE__ */ jsxRuntimeExports.jsx(
25466
25495
  "div",
25467
25496
  {
25468
- className: `w-3 h-3 rounded-full ${getTypeColor(
25497
+ className: `w-3 h-3 rounded-full flex-shrink-0 ${getTypeColor(
25469
25498
  selectedRequest.type
25470
25499
  )}`
25471
25500
  }
25472
25501
  ),
25473
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium", children: requestName }),
25502
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: requestName }),
25474
25503
  /* @__PURE__ */ jsxRuntimeExports.jsx(
25475
25504
  Badge,
25476
25505
  {
25477
25506
  variant: "outline",
25478
- className: `${getStatusColor(requestStatus)} border-current`,
25507
+ className: `${getStatusColor(
25508
+ requestStatus
25509
+ )} border-current flex-shrink-0`,
25479
25510
  children: requestStatus
25480
25511
  }
25481
25512
  )
@@ -25486,7 +25517,7 @@ const SidePanel = () => {
25486
25517
  variant: "ghost",
25487
25518
  size: "sm",
25488
25519
  onClick: onClose,
25489
- className: "h-6 w-6 p-0 text-gray-400 hover:text-blue-400",
25520
+ className: "h-6 w-6 p-0 text-gray-400 hover:text-blue-400 flex-shrink-0 ml-2",
25490
25521
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "h-4 w-4" })
25491
25522
  }
25492
25523
  )
@@ -562,6 +562,9 @@ video {
562
562
  .z-50 {
563
563
  z-index: 50;
564
564
  }
565
+ .col-span-2 {
566
+ grid-column: span 2 / span 2;
567
+ }
565
568
  .-m-2 {
566
569
  margin: -0.5rem;
567
570
  }
@@ -690,9 +693,6 @@ video {
690
693
  .w-3\.5 {
691
694
  width: 0.875rem;
692
695
  }
693
- .w-32 {
694
- width: 8rem;
695
- }
696
696
  .w-4 {
697
697
  width: 1rem;
698
698
  }
@@ -720,6 +720,9 @@ video {
720
720
  .flex-1 {
721
721
  flex: 1 1 0%;
722
722
  }
723
+ .flex-shrink-0 {
724
+ flex-shrink: 0;
725
+ }
723
726
  .shrink-0 {
724
727
  flex-shrink: 0;
725
728
  }
@@ -756,6 +759,9 @@ video {
756
759
  .grid-cols-5 {
757
760
  grid-template-columns: repeat(5, minmax(0, 1fr));
758
761
  }
762
+ .grid-cols-\[minmax\(7rem\2c 25\%\)_minmax\(3rem\2c 1fr\)\] {
763
+ grid-template-columns: minmax(7rem,25%) minmax(3rem,1fr);
764
+ }
759
765
  .flex-col {
760
766
  flex-direction: column;
761
767
  }
@@ -777,6 +783,13 @@ video {
777
783
  .gap-4 {
778
784
  gap: 1rem;
779
785
  }
786
+ .gap-x-2 {
787
+ -moz-column-gap: 0.5rem;
788
+ column-gap: 0.5rem;
789
+ }
790
+ .gap-y-2 {
791
+ row-gap: 0.5rem;
792
+ }
780
793
  .space-y-1 > :not([hidden]) ~ :not([hidden]) {
781
794
  --tw-space-y-reverse: 0;
782
795
  margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@@ -825,9 +838,6 @@ video {
825
838
  .whitespace-pre-wrap {
826
839
  white-space: pre-wrap;
827
840
  }
828
- .break-all {
829
- word-break: break-all;
830
- }
831
841
  .rounded {
832
842
  border-radius: 0.25rem;
833
843
  }
@@ -1221,6 +1231,9 @@ video {
1221
1231
  transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0));
1222
1232
  }
1223
1233
  }
1234
+ .wrap-anywhere {
1235
+ overflow-wrap: anywhere;
1236
+ }
1224
1237
  .file\:border-0::file-selector-button {
1225
1238
  border-width: 0px;
1226
1239
  }
@@ -1,5 +1,5 @@
1
- import { HttpNetworkEntry } from '../state/model';
1
+ import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
2
2
  export type CopyAsCurlButtonProps = {
3
- selectedRequest?: HttpNetworkEntry;
3
+ selectedRequest?: HttpNetworkEntry | SSENetworkEntry;
4
4
  };
5
5
  export declare const CopyAsCurlButton: ({ selectedRequest, }: CopyAsCurlButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+ export type KeyValueItem = {
3
+ key: string;
4
+ value: React.ReactNode;
5
+ keyClassName?: string;
6
+ valueClassName?: string;
7
+ };
8
+ export type KeyValueGridProps = {
9
+ items?: KeyValueItem[];
10
+ emptyMessage?: string;
11
+ className?: string;
12
+ };
13
+ export declare const KeyValueGrid: ({ items, emptyMessage, className, }: KeyValueGridProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,6 @@
1
+ import { default as React } from 'react';
2
+ export type SectionProps = {
3
+ title: string;
4
+ children: React.ReactNode;
5
+ };
6
+ export declare const Section: ({ title, children }: SectionProps) => import("react/jsx-runtime").JSX.Element;
@@ -105,6 +105,7 @@ function getFormDataEntries(formData) {
105
105
  }
106
106
  return [];
107
107
  }
108
+ const XMLHttpRequest = global.XMLHttpRequest || window.XMLHttpRequest;
108
109
  const originalXHROpen = XMLHttpRequest.prototype.open;
109
110
  const originalXHRSend = XMLHttpRequest.prototype.send;
110
111
  const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
@@ -265,6 +266,9 @@ function getRequestBody(body) {
265
266
  };
266
267
  }
267
268
  const getResponseSize = (request) => {
269
+ if (request.response === null) {
270
+ return 0;
271
+ }
268
272
  if (typeof request.response === "object") {
269
273
  return request.response.size;
270
274
  }
@@ -101,6 +101,7 @@ function getFormDataEntries(formData) {
101
101
  }
102
102
  return [];
103
103
  }
104
+ const XMLHttpRequest = global.XMLHttpRequest || window.XMLHttpRequest;
104
105
  const originalXHROpen = XMLHttpRequest.prototype.open;
105
106
  const originalXHRSend = XMLHttpRequest.prototype.send;
106
107
  const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
@@ -261,6 +262,9 @@ function getRequestBody(body) {
261
262
  };
262
263
  }
263
264
  const getResponseSize = (request) => {
265
+ if (request.response === null) {
266
+ return 0;
267
+ }
264
268
  if (typeof request.response === "object") {
265
269
  return request.response.size;
266
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.0.0-alpha.10-canary.1754923715388",
3
+ "version": "1.0.0-alpha.11",
4
4
  "description": "Network Activity for Rozenite.",
5
5
  "type": "module",
6
6
  "main": "./dist/react-native.cjs",
@@ -8,7 +8,7 @@
8
8
  "types": "./dist/react-native.d.ts",
9
9
  "dependencies": {
10
10
  "nanoevents": "^9.1.0",
11
- "@rozenite/plugin-bridge": "1.0.0-alpha.10-canary.1754923715388"
11
+ "@rozenite/plugin-bridge": "1.0.0-alpha.11"
12
12
  },
13
13
  "devDependencies": {
14
14
  "@floating-ui/react": "^0.26.0",
@@ -36,8 +36,8 @@
36
36
  "zustand": "^5.0.6",
37
37
  "@types/react": "~18.3.23",
38
38
  "@types/react-dom": "~18.3.1",
39
- "@rozenite/vite-plugin": "1.0.0-alpha.10-canary.1754923715388",
40
- "rozenite": "1.0.0-alpha.10-canary.1754923715388"
39
+ "rozenite": "1.0.0-alpha.11",
40
+ "@rozenite/vite-plugin": "1.0.0-alpha.11"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react-native-sse": "*"
@@ -52,6 +52,11 @@ function getRequestBody(body: XHRPostData): RequestPostData {
52
52
  }
53
53
 
54
54
  const getResponseSize = (request: XMLHttpRequest): number => {
55
+ // Handle a case of 204 where no-content was sent.
56
+ if (request.response === null) {
57
+ return 0;
58
+ }
59
+
55
60
  if (typeof request.response === 'object') {
56
61
  return request.response.size;
57
62
  }
@@ -11,6 +11,7 @@ import { XHRPostData } from '../../shared/client';
11
11
  * Source: https://github.com/facebook/react-native/blob/2c683c5787dd03ac15d2aad45dcc53650529ee7f/packages/react-native/src/private/devsupport/devmenu/elementinspector/XHRInterceptor.js
12
12
  */
13
13
 
14
+ const XMLHttpRequest = global.XMLHttpRequest || window.XMLHttpRequest;
14
15
  const originalXHROpen = XMLHttpRequest.prototype.open;
15
16
  const originalXHRSend = XMLHttpRequest.prototype.send;
16
17
  const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
@@ -2,10 +2,10 @@ import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
2
2
  import { Copy, Check } from 'lucide-react';
3
3
  import { Button } from './Button';
4
4
  import { generateCurlCommand } from '../utils/generateCurlCommand';
5
- import { HttpNetworkEntry } from '../state/model';
5
+ import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
6
6
 
7
7
  export type CopyAsCurlButtonProps = {
8
- selectedRequest?: HttpNetworkEntry;
8
+ selectedRequest?: HttpNetworkEntry | SSENetworkEntry;
9
9
  };
10
10
 
11
11
  export const CopyAsCurlButton = ({
@@ -0,0 +1,54 @@
1
+ import React, { Fragment } from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export type KeyValueItem = {
5
+ key: string;
6
+ value: React.ReactNode;
7
+ keyClassName?: string;
8
+ valueClassName?: string;
9
+ };
10
+
11
+ export type KeyValueGridProps = {
12
+ items?: KeyValueItem[];
13
+ emptyMessage?: string;
14
+ className?: string;
15
+ };
16
+
17
+ export const KeyValueGrid = ({
18
+ items = [],
19
+ emptyMessage,
20
+ className,
21
+ }: KeyValueGridProps) => {
22
+ const gridClassName = cn(
23
+ 'grid grid-cols-[minmax(7rem,25%)_minmax(3rem,1fr)] gap-x-2 gap-y-2 text-sm',
24
+ className
25
+ );
26
+
27
+ if (items.length === 0) {
28
+ return emptyMessage ? (
29
+ <div className={gridClassName}>
30
+ <span className="col-span-2 text-gray-500 italic">{emptyMessage}</span>
31
+ </div>
32
+ ) : null;
33
+ }
34
+
35
+ return (
36
+ <div className={gridClassName}>
37
+ {items.map((item, index) => (
38
+ <Fragment key={index}>
39
+ <span
40
+ className={cn(
41
+ 'text-gray-400 wrap-anywhere',
42
+ item.keyClassName
43
+ )}
44
+ >
45
+ {item.key}
46
+ </span>
47
+ <span className={cn('wrap-anywhere', item.valueClassName)}>
48
+ {item.value}
49
+ </span>
50
+ </Fragment>
51
+ ))}
52
+ </div>
53
+ );
54
+ };
@@ -18,6 +18,7 @@ const ScrollArea = React.forwardRef<
18
18
  {children}
19
19
  </ScrollAreaPrimitive.Viewport>
20
20
  <ScrollBar />
21
+ <ScrollBar orientation="horizontal" />
21
22
  <ScrollAreaPrimitive.Corner />
22
23
  </ScrollAreaPrimitive.Root>
23
24
  ));
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ export type SectionProps = {
4
+ title: string;
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ export const Section = ({ title, children }: SectionProps) => {
9
+ return (
10
+ <div>
11
+ <h4 className="text-sm font-medium text-gray-300 mb-2">{title}</h4>
12
+ {children}
13
+ </div>
14
+ );
15
+ };
@@ -278,16 +278,18 @@ export const SidePanel = () => {
278
278
  <div className="w-1/2 flex flex-col bg-gray-900">
279
279
  {/* Side Panel Header */}
280
280
  <div className="flex items-center justify-between p-3 border-b border-gray-700 bg-gray-800">
281
- <div className="flex items-center gap-2">
281
+ <div className="flex items-center gap-2 min-w-0 flex-1">
282
282
  <div
283
- className={`w-3 h-3 rounded-full ${getTypeColor(
283
+ className={`w-3 h-3 rounded-full flex-shrink-0 ${getTypeColor(
284
284
  selectedRequest.type
285
285
  )}`}
286
286
  ></div>
287
- <span className="font-medium">{requestName}</span>
287
+ <span className="font-medium truncate">{requestName}</span>
288
288
  <Badge
289
289
  variant="outline"
290
- className={`${getStatusColor(requestStatus)} border-current`}
290
+ className={`${getStatusColor(
291
+ requestStatus
292
+ )} border-current flex-shrink-0`}
291
293
  >
292
294
  {requestStatus}
293
295
  </Badge>
@@ -296,7 +298,7 @@ export const SidePanel = () => {
296
298
  variant="ghost"
297
299
  size="sm"
298
300
  onClick={onClose}
299
- className="h-6 w-6 p-0 text-gray-400 hover:text-blue-400"
301
+ className="h-6 w-6 p-0 text-gray-400 hover:text-blue-400 flex-shrink-0 ml-2"
300
302
  >
301
303
  <X className="h-4 w-4" />
302
304
  </Button>
@@ -6,6 +6,10 @@
6
6
  .text-balance {
7
7
  text-wrap: balance;
8
8
  }
9
+
10
+ .wrap-anywhere {
11
+ overflow-wrap: anywhere;
12
+ }
9
13
  }
10
14
 
11
15
  @layer base {
@@ -1,5 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { ScrollArea } from '../components/ScrollArea';
3
+ import { Section } from '../components/Section';
4
+ import { KeyValueGrid, KeyValueItem } from '../components/KeyValueGrid';
3
5
  import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
4
6
  import { getStatusColor } from '../utils/getStatusColor';
5
7
  import { CopyAsCurlButton } from '../components/CopyAsCurlButton';
@@ -9,11 +11,58 @@ export type HeadersTabProps = {
9
11
  };
10
12
 
11
13
  export const HeadersTab = ({ selectedRequest }: HeadersTabProps) => {
12
- const url = useMemo(() => {
13
- const { hostname, port, pathname } = new URL(selectedRequest.request.url);
14
+ const requestBody = selectedRequest.request.body;
14
15
 
15
- return `${hostname}${port ? `:${port}` : ''}${pathname}`;
16
- }, [selectedRequest.request.url]);
16
+ const generalItems: KeyValueItem[] = useMemo(
17
+ () => [
18
+ {
19
+ key: 'Request URL',
20
+ value: selectedRequest.request.url,
21
+ valueClassName: 'text-blue-400',
22
+ },
23
+ {
24
+ key: 'Request Method',
25
+ value: selectedRequest.request.method,
26
+ },
27
+ {
28
+ key: 'Status Code',
29
+ value: selectedRequest.response?.status ?? 'Pending',
30
+ valueClassName: getStatusColor(selectedRequest.response?.status ?? 0),
31
+ },
32
+ ...(requestBody
33
+ ? [
34
+ {
35
+ key: 'Content-Type',
36
+ value: requestBody.type,
37
+ valueClassName: 'text-blue-400',
38
+ },
39
+ ]
40
+ : []),
41
+ ],
42
+ [selectedRequest]
43
+ );
44
+
45
+ const responseHeadersItems: KeyValueItem[] = useMemo(() => {
46
+ const headers = selectedRequest.response?.headers;
47
+
48
+ if (!headers) return [];
49
+
50
+ return Object.entries(headers).map(([key, value]) => ({
51
+ key: key.toLowerCase(),
52
+ value,
53
+ }));
54
+ }, [selectedRequest.response?.headers]);
55
+
56
+ const requestHeadersItems: KeyValueItem[] = useMemo(() => {
57
+ const headers = selectedRequest.request.headers;
58
+
59
+ if (!headers) return [];
60
+
61
+ return Object.entries(headers).map(([key, value]) => ({
62
+ key: key.toLowerCase(),
63
+ value,
64
+ }));
65
+ }, [selectedRequest.request.headers]);
17
66
 
18
67
  const isCopyAsCurlEnabled =
19
68
  selectedRequest.request.body?.data.type !== 'binary';
@@ -24,93 +73,25 @@ export const HeadersTab = ({ selectedRequest }: HeadersTabProps) => {
24
73
  {isCopyAsCurlEnabled && (
25
74
  <CopyAsCurlButton selectedRequest={selectedRequest} />
26
75
  )}
27
- <div>
28
- <h4 className="text-sm font-medium text-gray-300 mb-2">General</h4>
29
- <div className="space-y-1 text-sm">
30
- <div className="flex">
31
- <span className="w-32 text-gray-400">Request URL:</span>
32
- <span className="text-blue-400">
33
- {url}
34
- </span>
35
- </div>
36
- <div className="flex">
37
- <span className="w-32 text-gray-400">Request Method:</span>
38
- <span>{selectedRequest.request.method}</span>
39
- </div>
40
- <div className="flex">
41
- <span className="w-32 text-gray-400">Status Code:</span>
42
- <span
43
- className={getStatusColor(
44
- selectedRequest.response?.status ?? 0
45
- )}
46
- >
47
- {selectedRequest.response?.status ?? 'Pending'}
48
- </span>
49
- </div>
50
- {selectedRequest.request.body && (
51
- <div className="flex">
52
- <span className="w-32 text-gray-400">Content-Type:</span>
53
- <span className="text-blue-400">
54
- {selectedRequest.request.body.type}
55
- </span>
56
- </div>
57
- )}
58
- </div>
59
- </div>
76
+ <Section title="General">
77
+ <KeyValueGrid items={generalItems} />
78
+ </Section>
60
79
 
61
- <div>
62
- <h4 className="text-sm font-medium text-gray-300 mb-2">
63
- Response Headers
64
- </h4>
65
- <div className="space-y-1 text-sm font-mono">
66
- {(() => {
67
- const responseHeaders = selectedRequest.response?.headers;
68
- if (responseHeaders && Object.keys(responseHeaders).length > 0) {
69
- return Object.entries(responseHeaders).map(([key, value]) => (
70
- <div key={key} className="flex">
71
- <span className="w-32 text-gray-400">
72
- {key.toLowerCase()}:
73
- </span>
74
- <span className="flex-1 break-all">{value}</span>
75
- </div>
76
- ));
77
- } else {
78
- return (
79
- <div className="text-gray-500 italic">
80
- No response headers available
81
- </div>
82
- );
83
- }
84
- })()}
85
- </div>
86
- </div>
80
+ <Section title="Response Headers">
81
+ <KeyValueGrid
82
+ items={responseHeadersItems}
83
+ emptyMessage="No response headers available"
84
+ className="font-mono"
85
+ />
86
+ </Section>
87
87
 
88
- <div>
89
- <h4 className="text-sm font-medium text-gray-300 mb-2">
90
- Request Headers
91
- </h4>
92
- <div className="space-y-1 text-sm font-mono">
93
- {(() => {
94
- const requestHeaders = selectedRequest.request.headers;
95
- if (requestHeaders && Object.keys(requestHeaders).length > 0) {
96
- return Object.entries(requestHeaders).map(([key, value]) => (
97
- <div key={key} className="flex">
98
- <span className="w-32 text-gray-400">
99
- {key.toLowerCase()}:
100
- </span>
101
- <span className="flex-1 break-all">{value}</span>
102
- </div>
103
- ));
104
- } else {
105
- return (
106
- <div className="text-gray-500 italic">
107
- No request headers available
108
- </div>
109
- );
110
- }
111
- })()}
112
- </div>
113
- </div>
88
+ <Section title="Request Headers">
89
+ <KeyValueGrid
90
+ items={requestHeadersItems}
91
+ emptyMessage="No request headers available"
92
+ className="font-mono"
93
+ />
94
+ </Section>
114
95
  </div>
115
96
  </ScrollArea>
116
97
  );