@djangocfg/layouts 2.0.8 → 2.0.10
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/package.json +5 -5
- package/src/auth/context/AuthContext.tsx +11 -6
- package/src/auth/hooks/useAuthGuard.ts +2 -2
- package/src/auth/hooks/useAutoAuth.ts +2 -2
- package/src/auth/hooks/useGithubAuth.ts +4 -3
- package/src/components/RedirectPage/RedirectPage.tsx +2 -2
- package/src/components/core/ClientOnly.tsx +73 -0
- package/src/components/core/index.ts +2 -0
- package/src/components/errors/ErrorLayout.tsx +6 -7
- package/src/layouts/AppLayout/AppLayout.tsx +25 -20
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +9 -21
- package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
- package/src/snippets/McpChat/components/AIChatWidget.tsx +3 -39
- package/src/snippets/McpChat/components/ChatMessages.tsx +2 -2
- package/src/snippets/McpChat/components/ChatPanel.tsx +84 -110
- package/src/snippets/McpChat/components/ChatSidebar.tsx +66 -60
- package/src/snippets/McpChat/components/ChatWidget.tsx +4 -37
- package/src/snippets/McpChat/components/MessageBubble.tsx +5 -5
- package/src/snippets/McpChat/components/index.ts +0 -2
- package/src/snippets/McpChat/config.ts +42 -0
- package/src/snippets/McpChat/context/ChatContext.tsx +5 -7
- package/src/snippets/McpChat/hooks/useChatLayout.ts +134 -23
- package/src/snippets/McpChat/index.ts +0 -1
- package/src/snippets/index.ts +0 -1
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
import { useEffect, useCallback, useRef, useState } from 'react';
|
|
4
|
+
import { useLocalStorage } from '@djangocfg/ui/hooks';
|
|
4
5
|
import type { ChatDisplayMode } from '../types';
|
|
6
|
+
import { sidebarConfig, fabConfig, storageKeys } from '../config';
|
|
7
|
+
|
|
8
|
+
// Re-export for convenience
|
|
9
|
+
export const MIN_SIDEBAR_WIDTH = sidebarConfig.minWidth;
|
|
10
|
+
export const MAX_SIDEBAR_WIDTH = sidebarConfig.maxWidth;
|
|
11
|
+
export const DEFAULT_SIDEBAR_WIDTH = sidebarConfig.defaultWidth;
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Configuration for chat layout management
|
|
8
15
|
*/
|
|
9
16
|
export interface ChatLayoutConfig {
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
/** Z-index for chat elements */
|
|
13
|
-
zIndex?: number;
|
|
17
|
+
/** Initial width of sidebar in pixels */
|
|
18
|
+
initialWidth?: number;
|
|
14
19
|
/** Animation duration in ms */
|
|
15
20
|
animationDuration?: number;
|
|
16
21
|
/** Element to push (defaults to body) */
|
|
@@ -21,10 +26,18 @@ export interface ChatLayoutConfig {
|
|
|
21
26
|
* Return type for useChatLayout hook
|
|
22
27
|
*/
|
|
23
28
|
export interface UseChatLayoutReturn {
|
|
29
|
+
/** Current sidebar width */
|
|
30
|
+
sidebarWidth: number;
|
|
24
31
|
/** Apply layout changes for mode */
|
|
25
32
|
applyLayout: (mode: ChatDisplayMode) => void;
|
|
26
33
|
/** Reset layout to default */
|
|
27
34
|
resetLayout: () => void;
|
|
35
|
+
/** Update sidebar width (for resize) */
|
|
36
|
+
updateWidth: (width: number) => void;
|
|
37
|
+
/** Start resize operation */
|
|
38
|
+
startResize: (e: React.MouseEvent) => void;
|
|
39
|
+
/** Whether currently resizing */
|
|
40
|
+
isResizing: boolean;
|
|
28
41
|
/** Get CSS for sidebar container */
|
|
29
42
|
getSidebarStyles: () => React.CSSProperties;
|
|
30
43
|
/** Get CSS for floating container */
|
|
@@ -34,9 +47,8 @@ export interface UseChatLayoutReturn {
|
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
const DEFAULT_CONFIG: Required<ChatLayoutConfig> = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
animationDuration: 200,
|
|
50
|
+
initialWidth: sidebarConfig.defaultWidth,
|
|
51
|
+
animationDuration: sidebarConfig.animationDuration,
|
|
40
52
|
pushTarget: 'body',
|
|
41
53
|
};
|
|
42
54
|
|
|
@@ -69,7 +81,22 @@ interface FixedElementOriginalStyles {
|
|
|
69
81
|
*/
|
|
70
82
|
export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
71
83
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
72
|
-
const {
|
|
84
|
+
const { initialWidth, animationDuration, pushTarget } = mergedConfig;
|
|
85
|
+
|
|
86
|
+
// Sidebar width with localStorage persistence
|
|
87
|
+
const [storedWidth, setStoredWidth] = useLocalStorage<number>(storageKeys.sidebarWidth, initialWidth);
|
|
88
|
+
|
|
89
|
+
// Clamp stored width to valid range
|
|
90
|
+
const sidebarWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, storedWidth));
|
|
91
|
+
const sidebarWidthRef = useRef(sidebarWidth);
|
|
92
|
+
|
|
93
|
+
// Resizing state (runtime only, not persisted)
|
|
94
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
95
|
+
|
|
96
|
+
// Keep ref in sync
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
sidebarWidthRef.current = sidebarWidth;
|
|
99
|
+
}, [sidebarWidth]);
|
|
73
100
|
|
|
74
101
|
// Store original styles for cleanup
|
|
75
102
|
const originalStylesRef = useRef<{
|
|
@@ -157,6 +184,7 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
157
184
|
*/
|
|
158
185
|
const adjustFixedElements = useCallback(
|
|
159
186
|
(open: boolean) => {
|
|
187
|
+
const currentWidth = sidebarWidthRef.current;
|
|
160
188
|
if (open) {
|
|
161
189
|
// Save and adjust fixed elements
|
|
162
190
|
const fixedElements = getFixedElements();
|
|
@@ -168,7 +196,7 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
168
196
|
|
|
169
197
|
fixedElements.forEach((el) => {
|
|
170
198
|
el.style.transition = `right ${animationDuration}ms ease`;
|
|
171
|
-
el.style.right = `${
|
|
199
|
+
el.style.right = `${currentWidth}px`;
|
|
172
200
|
});
|
|
173
201
|
} else {
|
|
174
202
|
// Restore fixed elements
|
|
@@ -185,7 +213,7 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
185
213
|
fixedElementsRef.current = [];
|
|
186
214
|
}
|
|
187
215
|
},
|
|
188
|
-
[getFixedElements,
|
|
216
|
+
[getFixedElements, animationDuration]
|
|
189
217
|
);
|
|
190
218
|
|
|
191
219
|
/**
|
|
@@ -195,11 +223,13 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
195
223
|
const target = getTargetElement();
|
|
196
224
|
if (!target) return;
|
|
197
225
|
|
|
226
|
+
const currentWidth = sidebarWidthRef.current;
|
|
227
|
+
|
|
198
228
|
saveOriginalStyles(target);
|
|
199
229
|
|
|
200
230
|
// Add smooth transition
|
|
201
231
|
target.style.transition = `margin-right ${animationDuration}ms ease`;
|
|
202
|
-
target.style.marginRight = `${
|
|
232
|
+
target.style.marginRight = `${currentWidth}px`;
|
|
203
233
|
target.style.overflowX = 'hidden';
|
|
204
234
|
target.setAttribute('data-chat-sidebar', 'open');
|
|
205
235
|
|
|
@@ -207,7 +237,7 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
207
237
|
adjustFixedElements(true);
|
|
208
238
|
|
|
209
239
|
currentModeRef.current = 'sidebar';
|
|
210
|
-
}, [getTargetElement, saveOriginalStyles,
|
|
240
|
+
}, [getTargetElement, saveOriginalStyles, animationDuration, adjustFixedElements]);
|
|
211
241
|
|
|
212
242
|
/**
|
|
213
243
|
* Apply floating/closed mode layout (reset sidebar push)
|
|
@@ -268,6 +298,83 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
268
298
|
currentModeRef.current = 'closed';
|
|
269
299
|
}, [getTargetElement, restoreOriginalStyles]);
|
|
270
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Update width during resize (no animation)
|
|
303
|
+
*/
|
|
304
|
+
const updateWidthImmediate = useCallback(
|
|
305
|
+
(newWidth: number) => {
|
|
306
|
+
const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, newWidth));
|
|
307
|
+
|
|
308
|
+
// Update body margin
|
|
309
|
+
const target = getTargetElement();
|
|
310
|
+
if (target && currentModeRef.current === 'sidebar') {
|
|
311
|
+
target.style.transition = 'none';
|
|
312
|
+
target.style.marginRight = `${clampedWidth}px`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Update fixed elements
|
|
316
|
+
fixedElementsRef.current.forEach(({ element }) => {
|
|
317
|
+
element.style.transition = 'none';
|
|
318
|
+
element.style.right = `${clampedWidth}px`;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return clampedWidth;
|
|
322
|
+
},
|
|
323
|
+
[getTargetElement]
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Update sidebar width (for resize)
|
|
328
|
+
*/
|
|
329
|
+
const updateWidth = useCallback(
|
|
330
|
+
(newWidth: number) => {
|
|
331
|
+
const clampedWidth = updateWidthImmediate(newWidth);
|
|
332
|
+
setStoredWidth(clampedWidth);
|
|
333
|
+
},
|
|
334
|
+
[updateWidthImmediate, setStoredWidth]
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Start resize operation
|
|
339
|
+
*/
|
|
340
|
+
const startResize = useCallback(
|
|
341
|
+
(e: React.MouseEvent) => {
|
|
342
|
+
e.preventDefault();
|
|
343
|
+
setIsResizing(true);
|
|
344
|
+
|
|
345
|
+
const startX = e.clientX;
|
|
346
|
+
const startWidth = sidebarWidthRef.current;
|
|
347
|
+
|
|
348
|
+
const handleMouseMove = (moveEvent: MouseEvent) => {
|
|
349
|
+
// Calculate new width (dragging left increases width)
|
|
350
|
+
const deltaX = startX - moveEvent.clientX;
|
|
351
|
+
const newWidth = startWidth + deltaX;
|
|
352
|
+
const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, newWidth));
|
|
353
|
+
|
|
354
|
+
// Update DOM immediately for smooth feel
|
|
355
|
+
updateWidthImmediate(clampedWidth);
|
|
356
|
+
sidebarWidthRef.current = clampedWidth;
|
|
357
|
+
|
|
358
|
+
// Also update React state so sidebar visually resizes
|
|
359
|
+
setStoredWidth(clampedWidth);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const handleMouseUp = () => {
|
|
363
|
+
setIsResizing(false);
|
|
364
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
365
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
366
|
+
document.body.style.cursor = '';
|
|
367
|
+
document.body.style.userSelect = '';
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
371
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
372
|
+
document.body.style.cursor = 'ew-resize';
|
|
373
|
+
document.body.style.userSelect = 'none';
|
|
374
|
+
},
|
|
375
|
+
[updateWidthImmediate, setStoredWidth]
|
|
376
|
+
);
|
|
377
|
+
|
|
271
378
|
/**
|
|
272
379
|
* Get CSS styles for sidebar container
|
|
273
380
|
*/
|
|
@@ -278,9 +385,9 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
278
385
|
right: 0,
|
|
279
386
|
bottom: 0,
|
|
280
387
|
width: `${sidebarWidth}px`,
|
|
281
|
-
zIndex,
|
|
388
|
+
zIndex: sidebarConfig.zIndex,
|
|
282
389
|
};
|
|
283
|
-
}, [sidebarWidth
|
|
390
|
+
}, [sidebarWidth]);
|
|
284
391
|
|
|
285
392
|
/**
|
|
286
393
|
* Get CSS styles for floating container
|
|
@@ -289,12 +396,12 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
289
396
|
(position: 'bottom-right' | 'bottom-left'): React.CSSProperties => {
|
|
290
397
|
return {
|
|
291
398
|
position: 'fixed',
|
|
292
|
-
zIndex: zIndex - 50,
|
|
293
|
-
bottom:
|
|
294
|
-
...(position === 'bottom-right' ? { right:
|
|
399
|
+
zIndex: sidebarConfig.zIndex - 50,
|
|
400
|
+
bottom: fabConfig.bottom,
|
|
401
|
+
...(position === 'bottom-right' ? { right: fabConfig.right } : { left: fabConfig.right }),
|
|
295
402
|
};
|
|
296
403
|
},
|
|
297
|
-
[
|
|
404
|
+
[]
|
|
298
405
|
);
|
|
299
406
|
|
|
300
407
|
/**
|
|
@@ -304,12 +411,12 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
304
411
|
(position: 'bottom-right' | 'bottom-left'): React.CSSProperties => {
|
|
305
412
|
return {
|
|
306
413
|
position: 'fixed',
|
|
307
|
-
zIndex: zIndex - 50,
|
|
308
|
-
bottom:
|
|
309
|
-
...(position === 'bottom-right' ? { right:
|
|
414
|
+
zIndex: sidebarConfig.zIndex - 50,
|
|
415
|
+
bottom: fabConfig.bottom,
|
|
416
|
+
...(position === 'bottom-right' ? { right: fabConfig.right } : { left: fabConfig.right }),
|
|
310
417
|
};
|
|
311
418
|
},
|
|
312
|
-
[
|
|
419
|
+
[]
|
|
313
420
|
);
|
|
314
421
|
|
|
315
422
|
// Cleanup on unmount
|
|
@@ -320,8 +427,12 @@ export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
|
|
|
320
427
|
}, [resetLayout]);
|
|
321
428
|
|
|
322
429
|
return {
|
|
430
|
+
sidebarWidth,
|
|
323
431
|
applyLayout,
|
|
324
432
|
resetLayout,
|
|
433
|
+
updateWidth,
|
|
434
|
+
startResize,
|
|
435
|
+
isResizing,
|
|
325
436
|
getSidebarStyles,
|
|
326
437
|
getFloatingStyles,
|
|
327
438
|
getFabStyles,
|