lynx-console 0.0.1 → 0.1.1

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 (26) hide show
  1. package/dist/assets/src/components/{BottomSheet.css.ts.vanilla-D-1A77Ik.css → BottomSheet.css.ts.vanilla-CulwSDhG.css} +2 -2
  2. package/dist/assets/src/components/ConsolePanel.css.ts.vanilla-DWdhFBJq.css +337 -0
  3. package/dist/assets/src/components/FadeList.css.ts.vanilla-sppTKMZj.css +12 -0
  4. package/dist/assets/src/components/{FloatingButton.css.ts.vanilla-rPj35oLW.css → FloatingButton.css.ts.vanilla-BaG0OI6p.css} +15 -3
  5. package/dist/assets/src/components/{NetworkPanel.css.ts.vanilla-DFMduT0T.css → NetworkPanel.css.ts.vanilla-BSE4s40D.css} +2 -5
  6. package/dist/assets/src/components/{PerformancePanel.css.ts.vanilla-D35LuXlW.css → PerformancePanel.css.ts.vanilla-Bb3zG5G8.css} +2 -2
  7. package/dist/index.cjs +743 -84
  8. package/dist/index.mjs +743 -84
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/components/BottomSheet.css.ts +2 -2
  12. package/src/components/ConsolePanel.css.ts +104 -16
  13. package/src/components/ConsolePanel.tsx +2 -1
  14. package/src/components/FadeList.css.ts +16 -0
  15. package/src/components/FadeList.tsx +76 -0
  16. package/src/components/FloatingButton.css.ts +15 -4
  17. package/src/components/FloatingButton.tsx +46 -10
  18. package/src/components/LogPanel.tsx +128 -15
  19. package/src/components/NetworkPanel.css.ts +2 -5
  20. package/src/components/NetworkPanel.tsx +4 -4
  21. package/src/components/PerformancePanel.css.ts +2 -2
  22. package/src/components/PerformancePanel.tsx +5 -5
  23. package/src/components/Tabs.tsx +3 -0
  24. package/src/hooks/useLongPressDrag.ts +95 -0
  25. package/src/index.tsx +1 -1
  26. package/dist/assets/src/components/ConsolePanel.css.ts.vanilla-B3avfSlI.css +0 -246
@@ -33,31 +33,128 @@ export const logHeader = style({
33
33
  flexDirection: "row",
34
34
  alignItems: "center",
35
35
  justifyContent: "space-between",
36
- marginBottom: 8,
37
- paddingBottom: 4,
36
+ paddingBottom: 3,
37
+ });
38
+
39
+ export const filterWrapper = style({
40
+ position: "relative",
41
+ });
42
+
43
+ export const filterButton = style({
44
+ display: "flex",
45
+ flexDirection: "row",
46
+ alignItems: "center",
47
+ padding: "3px 6px",
48
+ backgroundColor: vars.$color.bg.neutralWeak,
49
+ borderRadius: 4,
50
+ });
51
+
52
+ export const filterButtonText = style({
53
+ ...typography("t3", "medium"),
54
+ color: vars.$color.fg.neutralMuted,
55
+ });
56
+
57
+ export const filterDropdown = style({
58
+ position: "absolute",
59
+ top: "100%",
60
+ left: 0,
61
+ marginTop: 4,
62
+ backgroundColor: vars.$color.bg.layerFloating,
63
+ borderWidth: 1,
64
+ borderColor: vars.$color.stroke.neutralSubtle,
65
+ borderStyle: "solid",
66
+ borderRadius: 8,
67
+ padding: "4px 0",
68
+ zIndex: 100,
69
+ minWidth: 90,
70
+ });
71
+
72
+ export const filterOption = style({
73
+ display: "flex",
74
+ flexDirection: "row",
75
+ alignItems: "center",
76
+ gap: 4,
77
+ padding: "8px 12px",
78
+ });
79
+
80
+ export const filterCheckbox = recipe({
81
+ base: {
82
+ ...typography("t3", "medium"),
83
+ width: 16,
84
+ },
85
+ variants: {
86
+ level: {
87
+ log: { color: vars.$color.palette.green600 },
88
+ info: { color: vars.$color.palette.blue600 },
89
+ warn: { color: vars.$color.palette.yellow600 },
90
+ error: { color: vars.$color.palette.red600 },
91
+ },
92
+ },
93
+ });
94
+
95
+ export const filterLabel = recipe({
96
+ base: {
97
+ ...typography("t3", "medium"),
98
+ },
99
+ variants: {
100
+ level: {
101
+ log: { color: vars.$color.palette.green600 },
102
+ info: { color: vars.$color.palette.blue600 },
103
+ warn: { color: vars.$color.palette.yellow600 },
104
+ error: { color: vars.$color.palette.red600 },
105
+ },
106
+ },
107
+ });
108
+
109
+ export const searchWrapper = style({
110
+ display: "flex",
111
+ flexDirection: "row",
112
+ alignItems: "center",
113
+ flex: 1,
114
+ marginLeft: 8,
115
+ marginRight: 8,
38
116
  borderBottomWidth: 1,
39
117
  borderBottomColor: vars.$color.stroke.neutralSubtle,
40
118
  borderBottomStyle: "solid",
119
+ gap: 8,
120
+ });
121
+
122
+ export const searchPrompt = style({
123
+ ...typography("t6", "medium"),
124
+ color: vars.$color.fg.placeholder,
41
125
  });
42
126
 
43
- export const logCount = style({
127
+ export const searchInput = style({
128
+ flex: 1,
44
129
  ...typography("t3", "regular"),
45
- color: vars.$color.fg.neutralSubtle,
130
+ color: vars.$color.fg.neutral,
131
+ caretColor: vars.$color.palette.green600,
132
+ });
133
+
134
+ export const searchClear = style({
135
+ padding: "2px 4px",
136
+ });
137
+
138
+ export const searchClearText = style({
139
+ ...typography("t3", "medium"),
140
+ color: vars.$color.fg.placeholder,
46
141
  });
47
142
 
48
143
  export const clearButton = style({
49
- padding: "6px 12px",
144
+ padding: "3px 6px",
50
145
  backgroundColor: vars.$color.bg.neutralWeak,
51
146
  borderRadius: 4,
52
147
  });
53
148
 
54
149
  export const clearButtonText = style({
55
150
  ...typography("t3", "medium"),
56
- color: vars.$color.fg.neutral,
151
+ color: vars.$color.fg.neutralMuted,
57
152
  });
58
153
 
59
154
  export const logList = style({
60
155
  flex: 1,
156
+ paddingTop: 0,
157
+ paddingBottom: 0,
61
158
  });
62
159
 
63
160
  export const logItem = recipe({
@@ -221,17 +318,8 @@ export const replInputRow = style({
221
318
  flexDirection: "row",
222
319
  alignItems: "center",
223
320
  gap: 8,
224
- paddingTop: 8,
321
+ paddingTop: 0,
225
322
  paddingBottom: 8,
226
- marginTop: -1,
227
- borderTopWidth: 1,
228
- borderTopColor: vars.$color.stroke.neutralSubtle,
229
- borderTopStyle: "solid",
230
- backgroundImage: `linear-gradient(to bottom, transparent, ${vars.$color.bg.layerDefault})`,
231
- backgroundSize: "100% 32px",
232
- backgroundRepeat: "no-repeat",
233
- backgroundPosition: "top",
234
- backgroundColor: vars.$color.bg.layerDefault,
235
323
  });
236
324
 
237
325
  export const replPrompt = style({
@@ -1,6 +1,6 @@
1
1
  import { useConsole, useNetwork, usePerformance } from "../hooks";
2
2
  import * as css from "./ConsolePanel.css";
3
- import { LogPanel } from "./LogPanel";
3
+ import { LogPanel, dismissFilterDropdown } from "./LogPanel";
4
4
  import { NetworkPanel } from "./NetworkPanel";
5
5
  import { PerformancePanel } from "./PerformancePanel";
6
6
  import Tabs from "./Tabs";
@@ -13,6 +13,7 @@ export const ConsolePanel = () => {
13
13
  return (
14
14
  <view className={css.container}>
15
15
  <Tabs
16
+ onTabChange={dismissFilterDropdown}
16
17
  items={[
17
18
  {
18
19
  key: "log",
@@ -0,0 +1,16 @@
1
+ import { style } from "@vanilla-extract/css";
2
+ import { vars } from "../styles/vars";
3
+
4
+ export const fadeTop = style({
5
+ height: 20,
6
+ marginBottom: -20,
7
+ zIndex: 1,
8
+ background: `linear-gradient(to bottom, ${vars.$color.bg.layerFloating}, #ffffff00)`,
9
+ });
10
+
11
+ export const fadeBottom = style({
12
+ height: 20,
13
+ marginTop: -20,
14
+ zIndex: 1,
15
+ background: `linear-gradient(to top, ${vars.$color.bg.layerFloating}, #ffffff00)`,
16
+ });
@@ -0,0 +1,76 @@
1
+ import { useRef, useState } from "@lynx-js/react";
2
+ import type { BaseEvent, NodesRef } from "@lynx-js/types";
3
+ import { vars } from "../styles/vars";
4
+ import * as css from "./FadeList.css";
5
+
6
+ interface FadeListProps {
7
+ className?: string;
8
+ listRef?: React.RefObject<NodesRef>;
9
+ children: React.ReactNode;
10
+ "preload-buffer-count"?: number;
11
+ "initial-scroll-index"?: number;
12
+ }
13
+
14
+ export const FadeList = ({
15
+ className,
16
+ listRef: externalListRef,
17
+ children,
18
+ ...listProps
19
+ }: FadeListProps) => {
20
+ const [fadeState, setFadeState] = useState({ atTop: true, atBottom: true });
21
+ const fadeRef = useRef({ atTop: true, atBottom: true });
22
+ const internalListRef = useRef<NodesRef>(null);
23
+ const listRef = externalListRef ?? internalListRef;
24
+
25
+ return (
26
+ <>
27
+ <view
28
+ className={css.fadeTop}
29
+ style={{
30
+ background: fadeState.atTop
31
+ ? "linear-gradient(to bottom, #ffffff00, #ffffff00)"
32
+ : `linear-gradient(to bottom, ${vars.$color.bg.layerFloating}, #ffffff00)`,
33
+ }}
34
+ />
35
+ <list
36
+ ref={listRef}
37
+ scroll-orientation="vertical"
38
+ className={className}
39
+ scroll-event-throttle={16}
40
+ bindscroll={(
41
+ e: BaseEvent<
42
+ "bindscroll",
43
+ {
44
+ scrollTop: number;
45
+ scrollHeight: number;
46
+ listHeight: number;
47
+ }
48
+ >,
49
+ ) => {
50
+ const { scrollTop, scrollHeight, listHeight } = e.detail;
51
+ const atTop = scrollTop <= 10;
52
+ const atBottom = scrollTop + listHeight >= scrollHeight - 10;
53
+ if (
54
+ atTop !== fadeRef.current.atTop ||
55
+ atBottom !== fadeRef.current.atBottom
56
+ ) {
57
+ fadeRef.current.atTop = atTop;
58
+ fadeRef.current.atBottom = atBottom;
59
+ setFadeState({ atTop, atBottom });
60
+ }
61
+ }}
62
+ {...listProps}
63
+ >
64
+ {children}
65
+ </list>
66
+ <view
67
+ className={css.fadeBottom}
68
+ style={{
69
+ background: fadeState.atBottom
70
+ ? "linear-gradient(to top, #ffffff00, #ffffff00)"
71
+ : `linear-gradient(to top, ${vars.$color.bg.layerFloating}, #ffffff00)`,
72
+ }}
73
+ />
74
+ </>
75
+ );
76
+ };
@@ -4,18 +4,18 @@ import { vars } from "../styles/vars";
4
4
 
5
5
  export const wrapper = style({
6
6
  position: "fixed",
7
- right: "16px",
8
- bottom: "84px",
9
7
  zIndex: 9999,
10
8
  display: "flex",
11
9
  flexDirection: "row",
12
10
  alignItems: "center",
13
11
  gap: "8px",
12
+ overflow: "visible",
13
+ transition: `transform ${vars.$duration.d4} cubic-bezier(0.4, 0, 0.2, 1)`,
14
14
  });
15
15
 
16
- export const container = style({});
17
-
18
16
  export const button = style({
17
+ position: "relative",
18
+ overflow: "hidden",
19
19
  paddingLeft: "8px",
20
20
  paddingRight: "8px",
21
21
  paddingTop: "4px",
@@ -30,6 +30,16 @@ export const button = style({
30
30
  boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
31
31
  });
32
32
 
33
+ export const shineOverlay = style({
34
+ position: "absolute",
35
+ top: "-50%",
36
+ left: "-25%",
37
+ width: "150%",
38
+ height: "200%",
39
+ backgroundColor: "rgba(255, 255, 255, 0.2)",
40
+ borderRadius: "9999px",
41
+ });
42
+
33
43
  export const title = style({
34
44
  ...typography("t4", "regular"),
35
45
  color: vars.$color.palette.staticWhite,
@@ -43,6 +53,7 @@ export const subtitle = style({
43
53
  });
44
54
 
45
55
  export const reloadButton = style({
56
+ overflow: "visible",
46
57
  width: "32px",
47
58
  height: "32px",
48
59
  borderRadius: "16px",
@@ -1,18 +1,36 @@
1
1
  import type { ReactNode } from "@lynx-js/react";
2
+ import { useLongPressDrag } from "../hooks/useLongPressDrag";
2
3
  import * as css from "./FloatingButton.css";
3
4
 
4
5
  interface FloatingButtonProps {
5
6
  bindtap: () => void;
6
- isVisible: boolean;
7
7
  children: ReactNode;
8
8
  }
9
9
 
10
+ const SHINE_STYLES = {
11
+ idle: {
12
+ transform: "scale(0)",
13
+ opacity: 0,
14
+ },
15
+ dragging: {
16
+ transform: "scale(1)",
17
+ opacity: 1,
18
+ transition: "transform 300ms cubic-bezier(0.4, 0, 0.2, 1)",
19
+ },
20
+ releasing: {
21
+ transform: "scale(1)",
22
+ opacity: 0,
23
+ transition: "opacity 300ms cubic-bezier(0.4, 0, 0.2, 1)",
24
+ },
25
+ } as const;
26
+
10
27
  export const FloatingButton = ({
11
28
  bindtap,
12
- isVisible,
13
29
  children,
14
30
  }: FloatingButtonProps) => {
15
- if (!isVisible) return null;
31
+ const { phase, right, bottom, clearTimer, handlers } =
32
+ useLongPressDrag(bindtap);
33
+
16
34
 
17
35
  const handleReload = () => {
18
36
  try {
@@ -24,14 +42,32 @@ export const FloatingButton = ({
24
42
  }
25
43
  };
26
44
 
45
+ const isDragging = phase === "dragging";
46
+
27
47
  return (
28
- <view className={css.wrapper}>
29
- <view className={css.container} bindtap={bindtap}>
30
- <view className={css.button}>{children}</view>
31
- </view>
32
- <view className={css.reloadButton} bindtap={handleReload}>
33
- <text className={css.reloadIcon}>{"\u21BB"}</text>
48
+ <>
49
+ <view
50
+ className={css.wrapper}
51
+ consume-slide-event={[[-180, 180]]}
52
+ style={{
53
+ right: `${right}px`,
54
+ bottom: `${bottom}px`,
55
+ transform: isDragging ? "scale(1.05)" : "scale(1)",
56
+ }}
57
+ {...handlers}
58
+ >
59
+ <view className={css.button}>
60
+ {children}
61
+ <view className={css.shineOverlay} style={SHINE_STYLES[phase]} />
62
+ </view>
63
+ <view
64
+ className={css.reloadButton}
65
+ catchtouchstart={() => clearTimer()}
66
+ bindtap={handleReload}
67
+ >
68
+ <text className={css.reloadIcon}>{"\u21BB"}</text>
69
+ </view>
34
70
  </view>
35
- </view>
71
+ </>
36
72
  );
37
73
  };
@@ -1,8 +1,17 @@
1
- import { useEffect, useRef, useState } from "@lynx-js/react";
1
+ import { useEffect, useMemo, useRef, useState } from "@lynx-js/react";
2
2
  import type { BaseEvent, InputInputEvent, NodesRef } from "@lynx-js/types";
3
3
  import { stringify } from "javascript-stringify";
4
- import type { LogEntry } from "../types";
4
+ import type { LogEntry, LogLevel } from "../types";
5
5
  import * as css from "./ConsolePanel.css";
6
+ import { FadeList } from "./FadeList";
7
+
8
+ const LOG_LEVELS: LogLevel[] = ["log", "info", "warn", "error"];
9
+
10
+ let savedEnabledLevels: Set<LogLevel> | null = null;
11
+ let savedSearchQuery = "";
12
+ let closeFilterDropdown: (() => void) | null = null;
13
+
14
+ export const dismissFilterDropdown = () => closeFilterDropdown?.();
6
15
 
7
16
  interface LogPanelProps {
8
17
  logs: LogEntry[];
@@ -26,10 +35,62 @@ const runCode = (code: string) => {
26
35
  export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
27
36
  const [expandedArgs, setExpandedArgs] = useState(new Set());
28
37
  const [code, setCode] = useState("");
38
+ const [enabledLevels, setEnabledLevels] = useState<Set<LogLevel>>(
39
+ () => savedEnabledLevels ?? new Set(LOG_LEVELS),
40
+ );
41
+ const [filterOpen, setFilterOpen] = useState(false);
42
+ const [searchQuery, setSearchQuery] = useState(savedSearchQuery);
29
43
  const inputRef = useRef<NodesRef>(null);
44
+ const searchInputRef = useRef<NodesRef>(null);
30
45
  const listRef = useRef<NodesRef>(null);
31
- const logsRef = useRef(logs);
32
- logsRef.current = logs;
46
+
47
+ useEffect(() => {
48
+ savedEnabledLevels = enabledLevels;
49
+ }, [enabledLevels]);
50
+
51
+ useEffect(() => {
52
+ savedSearchQuery = searchQuery;
53
+ }, [searchQuery]);
54
+
55
+ useEffect(() => {
56
+ if (savedSearchQuery) {
57
+ searchInputRef.current
58
+ ?.invoke({ method: "setValue", params: { value: savedSearchQuery } })
59
+ .exec();
60
+ }
61
+ }, []);
62
+
63
+ useEffect(() => {
64
+ closeFilterDropdown = () => setFilterOpen(false);
65
+ return () => { closeFilterDropdown = null; };
66
+ }, []);
67
+
68
+ const filteredLogs = useMemo(
69
+ () =>
70
+ logs.filter((log) => {
71
+ if (!enabledLevels.has(log.level)) return false;
72
+ if (searchQuery) {
73
+ const query = searchQuery.toLowerCase();
74
+ return log.args.some((arg) => String(arg).toLowerCase().includes(query));
75
+ }
76
+ return true;
77
+ }),
78
+ [logs, enabledLevels, searchQuery],
79
+ );
80
+ const logsRef = useRef(filteredLogs);
81
+ logsRef.current = filteredLogs;
82
+
83
+ const toggleLevel = (level: LogLevel) => {
84
+ setEnabledLevels((prev) => {
85
+ const next = new Set(prev);
86
+ if (next.has(level)) {
87
+ next.delete(level);
88
+ } else {
89
+ next.add(level);
90
+ }
91
+ return next;
92
+ });
93
+ };
33
94
 
34
95
  const scrollToBottom = (smooth: boolean) => {
35
96
  if (logsRef.current.length === 0) return;
@@ -43,7 +104,7 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
43
104
 
44
105
  useEffect(() => {
45
106
  scrollToBottom(true);
46
- }, [logs]);
107
+ }, [filteredLogs]);
47
108
 
48
109
  const toggleArg = (key: string) => {
49
110
  setExpandedArgs((prev) => {
@@ -165,22 +226,74 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
165
226
  };
166
227
 
167
228
  return (
168
- <view className={css.logContainer}>
229
+ <view
230
+ className={css.logContainer}
231
+ bindtap={() => { if (filterOpen) setFilterOpen(false); }}
232
+ >
169
233
  <view className={css.logHeader}>
170
- <text className={css.logCount}>Total {logs.length} logs</text>
234
+ <view className={css.filterWrapper}>
235
+ <view
236
+ className={css.filterButton}
237
+ catchtap={() => setFilterOpen((v) => !v)}
238
+ >
239
+ <text className={css.filterButtonText}>Filter ▼</text>
240
+ </view>
241
+ {filterOpen && (
242
+ <view className={css.filterDropdown} catchtap={() => {}}>
243
+ {LOG_LEVELS.map((level) => (
244
+ <view
245
+ key={level}
246
+ className={css.filterOption}
247
+ bindtap={() => toggleLevel(level)}
248
+ >
249
+ <text className={css.filterCheckbox({ level })}>
250
+ {enabledLevels.has(level) ? "✅" : "⬜"}
251
+ </text>
252
+ <text className={css.filterLabel({ level })}>
253
+ {level.toUpperCase()}
254
+ </text>
255
+ </view>
256
+ ))}
257
+ </view>
258
+ )}
259
+ </view>
260
+ <view className={css.searchWrapper}>
261
+ <text className={css.searchPrompt}>{"›"}</text>
262
+ <input
263
+ ref={searchInputRef}
264
+ className={css.searchInput}
265
+ placeholder="Search logs..."
266
+ bindinput={(e: BaseEvent<"bindinput", InputInputEvent>) =>
267
+ setSearchQuery(e.detail.value)
268
+ }
269
+ />
270
+ {searchQuery.length > 0 && (
271
+ <view
272
+ className={css.searchClear}
273
+ bindtap={() => {
274
+ setSearchQuery("");
275
+ searchInputRef.current
276
+ ?.invoke({ method: "setValue", params: { value: "" } })
277
+ .exec();
278
+ }}
279
+ >
280
+ <text className={css.searchClearText}>✕</text>
281
+ </view>
282
+ )}
283
+ </view>
171
284
  <view style={{ display: "flex", flexDirection: "row", gap: 8 }}>
172
285
  <view className={css.clearButton} bindtap={clearLogs}>
173
- <text className={css.clearButtonText}>Clear</text>
286
+ <text className={css.clearButtonText}>🗑</text>
174
287
  </view>
175
288
  </view>
176
289
  </view>
177
- <list
178
- ref={listRef}
179
- scroll-orientation="vertical"
290
+ <FadeList
291
+ listRef={listRef}
180
292
  className={css.logList}
181
- initial-scroll-index={Math.max(0, logs.length - 1)}
293
+ preload-buffer-count={10}
294
+ initial-scroll-index={Math.max(0, filteredLogs.length - 1)}
182
295
  >
183
- {logs.length === 0 ? (
296
+ {filteredLogs.length === 0 ? (
184
297
  <list-item item-key="empty-state">
185
298
  <view className={css.placeholder}>
186
299
  <text className={css.placeholderText}>
@@ -189,7 +302,7 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
189
302
  </view>
190
303
  </list-item>
191
304
  ) : (
192
- logs.map((log) => {
305
+ filteredLogs.map((log) => {
193
306
  return (
194
307
  <list-item key={log.id} item-key={log.id}>
195
308
  <view className={css.logItem({ level: log.level })}>
@@ -220,7 +333,7 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
220
333
  );
221
334
  })
222
335
  )}
223
- </list>
336
+ </FadeList>
224
337
  <view className={css.replInputRow}>
225
338
  <text className={css.replPrompt}>{"›"}</text>
226
339
  <input
@@ -17,9 +17,6 @@ export const header = style({
17
17
  justifyContent: "space-between",
18
18
  marginBottom: 8,
19
19
  paddingBottom: 4,
20
- borderBottomWidth: 1,
21
- borderBottomColor: vars.$color.stroke.neutralSubtle,
22
- borderBottomStyle: "solid",
23
20
  });
24
21
 
25
22
  export const count = style({
@@ -28,14 +25,14 @@ export const count = style({
28
25
  });
29
26
 
30
27
  export const clearButton = style({
31
- padding: "6px 12px",
28
+ padding: "3px 6px",
32
29
  backgroundColor: vars.$color.bg.neutralWeak,
33
30
  borderRadius: 4,
34
31
  });
35
32
 
36
33
  export const clearButtonText = style({
37
34
  ...typography("t3", "medium"),
38
- color: vars.$color.fg.neutral,
35
+ color: vars.$color.fg.neutralMuted,
39
36
  });
40
37
 
41
38
  export const list = style({
@@ -1,5 +1,6 @@
1
1
  import { useState } from "@lynx-js/react";
2
2
  import type { NetworkEntry } from "../types";
3
+ import { FadeList } from "./FadeList";
3
4
  import { NetworkDetailSection } from "./NetworkDetailSection";
4
5
  import * as css from "./NetworkPanel.css";
5
6
 
@@ -16,7 +17,6 @@ export const NetworkPanel = ({
16
17
  }: NetworkPanelProps) => {
17
18
  const [selectedId, setSelectedId] = useState<string | null>(null);
18
19
  const [activeTab, setActiveTab] = useState<TabType>("general");
19
-
20
20
  const formatDuration = (duration?: number): string => {
21
21
  if (!duration) return "-";
22
22
  if (duration < 1000) return `${duration}ms`;
@@ -71,7 +71,7 @@ export const NetworkPanel = ({
71
71
  <view className={css.header}>
72
72
  <text className={css.count}>Total: {networks.length} requests</text>
73
73
  <view className={css.clearButton} bindtap={clearNetworks}>
74
- <text className={css.clearButtonText}>Clear</text>
74
+ <text className={css.clearButtonText}>🗑</text>
75
75
  </view>
76
76
  </view>
77
77
 
@@ -80,7 +80,7 @@ export const NetworkPanel = ({
80
80
  <text className={css.placeholderText}>No network requests yet</text>
81
81
  </view>
82
82
  ) : (
83
- <list className={css.list}>
83
+ <FadeList className={css.list}>
84
84
  {networks.map((network) => (
85
85
  <list-item key={network.id} item-key={network.id}>
86
86
  <view className={css.item({ status: network.status })}>
@@ -215,7 +215,7 @@ export const NetworkPanel = ({
215
215
  </view>
216
216
  </list-item>
217
217
  ))}
218
- </list>
218
+ </FadeList>
219
219
  )}
220
220
  </view>
221
221
  );
@@ -28,14 +28,14 @@ export const count = style({
28
28
  });
29
29
 
30
30
  export const clearButton = style({
31
- padding: "6px 12px",
31
+ padding: "3px 6px",
32
32
  backgroundColor: vars.$color.bg.neutralWeak,
33
33
  borderRadius: 4,
34
34
  });
35
35
 
36
36
  export const clearButtonText = style({
37
37
  ...typography("t3", "medium"),
38
- color: vars.$color.fg.neutral,
38
+ color: vars.$color.fg.neutralMuted,
39
39
  });
40
40
 
41
41
  export const list = style({