@nextclaw/ui 0.4.0 → 0.5.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.
package/dist/index.html CHANGED
@@ -6,8 +6,8 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw - 系统配置</title>
9
- <script type="module" crossorigin src="/assets/index-D16PLMLv.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-Wn63frSd.css">
9
+ <script type="module" crossorigin src="/assets/index-B_-o3-kG.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-B6sMhZ9q.css">
11
11
  </head>
12
12
 
13
13
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,7 +31,11 @@ export function DocBrowser() {
31
31
 
32
32
  const [urlInput, setUrlInput] = useState('');
33
33
  const [isDragging, setIsDragging] = useState(false);
34
- const [floatPos, setFloatPos] = useState({ x: 120, y: 80 });
34
+ const [isResizing, setIsResizing] = useState(false);
35
+ const [floatPos, setFloatPos] = useState(() => ({
36
+ x: Math.max(40, window.innerWidth - 520),
37
+ y: 80,
38
+ }));
35
39
  const [floatSize, setFloatSize] = useState({ w: 480, h: 600 });
36
40
  const [dockedWidth, setDockedWidth] = useState(420);
37
41
  const dragRef = useRef<{ startX: number; startY: number; startPosX: number; startPosY: number } | null>(null);
@@ -48,6 +52,16 @@ export function DocBrowser() {
48
52
  }
49
53
  }, [currentUrl]);
50
54
 
55
+ // Reposition floating window near right edge when switching from docked
56
+ useEffect(() => {
57
+ if (mode === 'floating') {
58
+ setFloatPos(prev => ({
59
+ x: Math.max(40, window.innerWidth - floatSize.w - 40),
60
+ y: prev.y,
61
+ }));
62
+ }
63
+ }, [mode, floatSize.w]);
64
+
51
65
  // Listen for route changes from the iframe via postMessage
52
66
  useEffect(() => {
53
67
  const handler = (e: MessageEvent) => {
@@ -105,10 +119,12 @@ export function DocBrowser() {
105
119
  };
106
120
  }, [isDragging]);
107
121
 
108
- // --- Resize logic (floating mode — bottom-right corner) ---
122
+ // --- Resize logic (floating mode) ---
109
123
  const onResizeStart = useCallback((e: React.MouseEvent) => {
110
124
  e.preventDefault();
111
125
  e.stopPropagation();
126
+ setIsResizing(true);
127
+ const axis = (e.currentTarget as HTMLElement).dataset.axis; // 'x', 'y', or undefined (both)
112
128
  resizeRef.current = {
113
129
  startX: e.clientX,
114
130
  startY: e.clientY,
@@ -117,12 +133,13 @@ export function DocBrowser() {
117
133
  };
118
134
  const onMove = (ev: MouseEvent) => {
119
135
  if (!resizeRef.current) return;
120
- setFloatSize({
121
- w: Math.max(360, resizeRef.current.startW + (ev.clientX - resizeRef.current.startX)),
122
- h: Math.max(400, resizeRef.current.startH + (ev.clientY - resizeRef.current.startY)),
123
- });
136
+ setFloatSize(prev => ({
137
+ w: axis === 'y' ? prev.w : Math.max(360, resizeRef.current!.startW + (ev.clientX - resizeRef.current!.startX)),
138
+ h: axis === 'x' ? prev.h : Math.max(400, resizeRef.current!.startH + (ev.clientY - resizeRef.current!.startY)),
139
+ }));
124
140
  };
125
141
  const onUp = () => {
142
+ setIsResizing(false);
126
143
  resizeRef.current = null;
127
144
  window.removeEventListener('mousemove', onMove);
128
145
  window.removeEventListener('mouseup', onUp);
@@ -135,14 +152,15 @@ export function DocBrowser() {
135
152
  const onDockResizeStart = useCallback((e: React.MouseEvent) => {
136
153
  e.preventDefault();
137
154
  e.stopPropagation();
155
+ setIsResizing(true);
138
156
  dockResizeRef.current = { startX: e.clientX, startW: dockedWidth };
139
157
  const onMove = (ev: MouseEvent) => {
140
158
  if (!dockResizeRef.current) return;
141
- // Dragging left should increase width (since resize handle is on the left edge)
142
159
  const delta = dockResizeRef.current.startX - ev.clientX;
143
160
  setDockedWidth(Math.max(320, Math.min(800, dockResizeRef.current.startW + delta)));
144
161
  };
145
162
  const onUp = () => {
163
+ setIsResizing(false);
146
164
  dockResizeRef.current = null;
147
165
  window.removeEventListener('mousemove', onMove);
148
166
  window.removeEventListener('mouseup', onUp);
@@ -246,12 +264,17 @@ export function DocBrowser() {
246
264
  {/* Iframe Content */}
247
265
  <div className="flex-1 relative overflow-hidden">
248
266
  <iframe
267
+ key={currentUrl}
249
268
  src={currentUrl}
250
269
  className="absolute inset-0 w-full h-full border-0"
251
270
  title="NextClaw Documentation"
252
271
  sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
253
272
  allow="clipboard-read; clipboard-write"
254
273
  />
274
+ {/* Transparent overlay during resize to prevent iframe from stealing mouse events */}
275
+ {(isResizing || isDragging) && (
276
+ <div className="absolute inset-0 z-10" />
277
+ )}
255
278
  </div>
256
279
 
257
280
  {/* Footer */}
@@ -267,14 +290,21 @@ export function DocBrowser() {
267
290
  </a>
268
291
  </div>
269
292
 
270
- {/* Resize Handle (floating only — bottom-right corner) */}
293
+ {/* Resize Handles (floating only) */}
271
294
  {!isDocked && (
272
- <div
273
- className="absolute bottom-0 right-0 w-5 h-5 cursor-se-resize flex items-center justify-center text-gray-300 hover:text-gray-500 transition-colors"
274
- onMouseDown={onResizeStart}
275
- >
276
- <GripVertical className="w-3 h-3 rotate-[-45deg]" />
277
- </div>
295
+ <>
296
+ {/* Right edge */}
297
+ <div className="absolute top-0 right-0 w-1.5 h-full cursor-ew-resize z-20 hover:bg-primary/10 transition-colors" onMouseDown={onResizeStart} data-axis="x" />
298
+ {/* Bottom edge */}
299
+ <div className="absolute bottom-0 left-0 h-1.5 w-full cursor-ns-resize z-20 hover:bg-primary/10 transition-colors" onMouseDown={onResizeStart} data-axis="y" />
300
+ {/* Bottom-right corner */}
301
+ <div
302
+ className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize z-30 flex items-center justify-center text-gray-300 hover:text-gray-500 transition-colors"
303
+ onMouseDown={onResizeStart}
304
+ >
305
+ <GripVertical className="w-3 h-3 rotate-[-45deg]" />
306
+ </div>
307
+ </>
278
308
  )}
279
309
  </div>
280
310
  );
@@ -86,12 +86,20 @@ export function DocBrowserProvider({ children }: { children: ReactNode }) {
86
86
  }, []);
87
87
 
88
88
  const navigate = useCallback((url: string) => {
89
- setState(prev => ({
90
- ...prev,
91
- currentUrl: url,
92
- history: [...prev.history.slice(0, prev.historyIndex + 1), url],
93
- historyIndex: prev.historyIndex + 1,
94
- }));
89
+ setState(prev => {
90
+ // Normalize URLs for comparison (strip trailing slash, .html suffix)
91
+ const normalize = (u: string) => {
92
+ try { return new URL(u).pathname.replace(/\.html$/, '').replace(/\/$/, ''); } catch { return u; }
93
+ };
94
+ // Skip if same as current URL (prevents loop from postMessage echo)
95
+ if (normalize(url) === normalize(prev.currentUrl)) return prev;
96
+ return {
97
+ ...prev,
98
+ currentUrl: url,
99
+ history: [...prev.history.slice(0, prev.historyIndex + 1), url],
100
+ historyIndex: prev.historyIndex + 1,
101
+ };
102
+ });
95
103
  }, []);
96
104
 
97
105
  const goBack = useCallback(() => {
@@ -18,6 +18,9 @@ export function useDocLinkInterceptor() {
18
18
  const href = anchor.getAttribute('href') || '';
19
19
  if (!isDocsUrl(href)) return;
20
20
 
21
+ // Don't intercept links explicitly marked for external opening
22
+ if (anchor.hasAttribute('data-doc-external')) return;
23
+
21
24
  // Don't intercept if modifier keys are held (user wants new tab behavior)
22
25
  if (e.ctrlKey || e.metaKey || e.shiftKey) return;
23
26
 
package/src/lib/i18n.ts CHANGED
@@ -164,6 +164,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
164
164
  // Doc Browser
165
165
  docBrowserTitle: { zh: '帮助文档', en: 'Help Docs' },
166
166
  docBrowserSearchPlaceholder: { zh: '搜索,也可以输入文档地址直接打开', en: 'Search, or enter a doc URL to open' },
167
+ docBrowserUrlPlaceholder: { zh: '输入文档路径,如 /guide/channels', en: 'Enter a doc path, e.g. /guide/channels' },
167
168
  docBrowserOpenExternal: { zh: '文档中心打开', en: 'Open in Docs' },
168
169
  docBrowserFloatMode: { zh: '悬浮窗口', en: 'Float Window' },
169
170
  docBrowserDockMode: { zh: '固定到侧栏', en: 'Dock to Sidebar' },