@djangocfg/layouts 2.1.226 → 2.1.228

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 (97) hide show
  1. package/README.md +3 -17
  2. package/package.json +18 -18
  3. package/src/components/errors/ErrorLayout.tsx +2 -2
  4. package/src/components/errors/ErrorsTracker/index.ts +1 -0
  5. package/src/components/errors/ErrorsTracker/utils/formatters.ts +23 -1
  6. package/src/hooks/useLogout.ts +9 -12
  7. package/src/layouts/AppLayout/AppLayout.tsx +20 -8
  8. package/src/layouts/AppLayout/BaseApp.tsx +5 -28
  9. package/src/layouts/AuthLayout/AuthLayout.tsx +51 -22
  10. package/src/layouts/AuthLayout/README.md +78 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +2 -2
  12. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -2
  13. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +2 -2
  14. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +3 -2
  15. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +4 -1
  16. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +2 -2
  17. package/src/layouts/AuthLayout/components/shared/index.ts +0 -2
  18. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +25 -80
  19. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +8 -13
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +2 -2
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +2 -2
  22. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +2 -2
  23. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +61 -42
  24. package/src/layouts/AuthLayout/context.tsx +0 -2
  25. package/src/layouts/AuthLayout/index.ts +9 -6
  26. package/src/layouts/AuthLayout/styles/auth.css +265 -120
  27. package/src/layouts/AuthLayout/types.ts +60 -7
  28. package/src/layouts/ProfileLayout/.claude/.sidecar/activity.jsonl +2 -0
  29. package/src/layouts/ProfileLayout/.claude/.sidecar/history/2026-03-15.md +35 -0
  30. package/src/layouts/ProfileLayout/.claude/.sidecar/review.md +35 -0
  31. package/src/layouts/ProfileLayout/.claude/.sidecar/scan.log +3 -0
  32. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-001.md +18 -0
  33. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-002.md +19 -0
  34. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-003.md +18 -0
  35. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-004.md +18 -0
  36. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-005.md +18 -0
  37. package/src/layouts/ProfileLayout/.claude/.sidecar/usage.json +5 -0
  38. package/src/layouts/ProfileLayout/ProfileLayout.tsx +52 -403
  39. package/src/layouts/ProfileLayout/components/ActionButton.tsx +38 -0
  40. package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +109 -148
  41. package/src/layouts/ProfileLayout/components/EditableField.tsx +119 -0
  42. package/src/layouts/ProfileLayout/components/Section.tsx +22 -0
  43. package/src/layouts/ProfileLayout/components/index.ts +4 -1
  44. package/src/layouts/ProfileLayout/context.tsx +31 -0
  45. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
  46. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
  47. package/src/layouts/_components/UserMenu.tsx +2 -2
  48. package/src/layouts/types/README.md +0 -20
  49. package/src/layouts/types/index.ts +2 -2
  50. package/src/layouts/types/layout.types.ts +2 -5
  51. package/src/layouts/types/providers.types.ts +0 -27
  52. package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
  53. package/src/snippets/Breadcrumbs.tsx +2 -2
  54. package/src/snippets/index.ts +0 -67
  55. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +0 -56
  56. package/src/snippets/McpChat/README.md +0 -441
  57. package/src/snippets/McpChat/components/AIChatWidget.tsx +0 -361
  58. package/src/snippets/McpChat/components/AskAIButton.tsx +0 -92
  59. package/src/snippets/McpChat/components/ChatMessages.tsx +0 -138
  60. package/src/snippets/McpChat/components/ChatPanel.tsx +0 -131
  61. package/src/snippets/McpChat/components/ChatSidebar.tsx +0 -156
  62. package/src/snippets/McpChat/components/ChatWidget.tsx +0 -115
  63. package/src/snippets/McpChat/components/MessageBubble.tsx +0 -142
  64. package/src/snippets/McpChat/components/MessageInput.tsx +0 -140
  65. package/src/snippets/McpChat/components/index.ts +0 -24
  66. package/src/snippets/McpChat/config.ts +0 -94
  67. package/src/snippets/McpChat/context/AIChatContext.tsx +0 -327
  68. package/src/snippets/McpChat/context/ChatContext.tsx +0 -361
  69. package/src/snippets/McpChat/context/index.ts +0 -7
  70. package/src/snippets/McpChat/hooks/index.ts +0 -6
  71. package/src/snippets/McpChat/hooks/useAIChat.ts +0 -503
  72. package/src/snippets/McpChat/hooks/useChatLayout.ts +0 -442
  73. package/src/snippets/McpChat/hooks/useMcpChat.ts +0 -90
  74. package/src/snippets/McpChat/index.ts +0 -79
  75. package/src/snippets/McpChat/types.ts +0 -189
  76. package/src/snippets/PWAInstall/@docs/README.md +0 -92
  77. package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +0 -576
  78. package/src/snippets/PWAInstall/README.md +0 -235
  79. package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -236
  80. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +0 -234
  81. package/src/snippets/PWAInstall/components/IOSGuide.tsx +0 -29
  82. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +0 -103
  83. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +0 -103
  84. package/src/snippets/PWAInstall/components/PWAPageResumeManager.tsx +0 -33
  85. package/src/snippets/PWAInstall/context/InstallContext.tsx +0 -102
  86. package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +0 -168
  87. package/src/snippets/PWAInstall/hooks/useIsPWA.ts +0 -116
  88. package/src/snippets/PWAInstall/hooks/usePWAPageResume.ts +0 -163
  89. package/src/snippets/PWAInstall/index.ts +0 -80
  90. package/src/snippets/PWAInstall/types/components.ts +0 -95
  91. package/src/snippets/PWAInstall/types/config.ts +0 -29
  92. package/src/snippets/PWAInstall/types/index.ts +0 -26
  93. package/src/snippets/PWAInstall/types/install.ts +0 -38
  94. package/src/snippets/PWAInstall/types/platform.ts +0 -29
  95. package/src/snippets/PWAInstall/utils/localStorage.ts +0 -181
  96. package/src/snippets/PWAInstall/utils/logger.ts +0 -149
  97. package/src/snippets/PWAInstall/utils/platform.ts +0 -151
@@ -1,442 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback, useEffect, useRef, useState } from 'react';
4
-
5
- import { useLocalStorage } from '@djangocfg/ui-core/hooks';
6
-
7
- import { fabConfig, sidebarConfig, storageKeys } from '../config';
8
-
9
- import type { ChatDisplayMode } from '../types';
10
- // Re-export for convenience
11
- export const MIN_SIDEBAR_WIDTH = sidebarConfig.minWidth;
12
- export const MAX_SIDEBAR_WIDTH = sidebarConfig.maxWidth;
13
- export const DEFAULT_SIDEBAR_WIDTH = sidebarConfig.defaultWidth;
14
-
15
- /**
16
- * Configuration for chat layout management
17
- */
18
- export interface ChatLayoutConfig {
19
- /** Initial width of sidebar in pixels */
20
- initialWidth?: number;
21
- /** Animation duration in ms */
22
- animationDuration?: number;
23
- /** Element to push (defaults to body) */
24
- pushTarget?: 'body' | 'main' | string;
25
- }
26
-
27
- /**
28
- * Return type for useChatLayout hook
29
- */
30
- export interface UseChatLayoutReturn {
31
- /** Current sidebar width */
32
- sidebarWidth: number;
33
- /** Apply layout changes for mode */
34
- applyLayout: (mode: ChatDisplayMode) => void;
35
- /** Reset layout to default */
36
- resetLayout: () => void;
37
- /** Update sidebar width (for resize) */
38
- updateWidth: (width: number) => void;
39
- /** Start resize operation */
40
- startResize: (e: React.MouseEvent) => void;
41
- /** Whether currently resizing */
42
- isResizing: boolean;
43
- /** Get CSS for sidebar container */
44
- getSidebarStyles: () => React.CSSProperties;
45
- /** Get CSS for floating container */
46
- getFloatingStyles: (position: 'bottom-right' | 'bottom-left') => React.CSSProperties;
47
- /** Get CSS for FAB button */
48
- getFabStyles: (position: 'bottom-right' | 'bottom-left') => React.CSSProperties;
49
- }
50
-
51
- const DEFAULT_CONFIG: Required<ChatLayoutConfig> = {
52
- initialWidth: sidebarConfig.defaultWidth,
53
- animationDuration: sidebarConfig.animationDuration,
54
- pushTarget: 'body',
55
- };
56
-
57
- /** Stored original styles for fixed elements */
58
- interface FixedElementOriginalStyles {
59
- element: HTMLElement;
60
- right: string;
61
- transition: string;
62
- }
63
-
64
- /**
65
- * Hook for managing chat layout embedding modes
66
- *
67
- * Handles:
68
- * - Sidebar mode: pushes content left by adding margin to target element
69
- * AND automatically adjusts all position:fixed elements with right:0
70
- * - Floating mode: positions chat at bottom-right/left
71
- * - Closed mode: just shows FAB button
72
- *
73
- * @example
74
- * ```tsx
75
- * const { applyLayout, getSidebarStyles, getFloatingStyles } = useChatLayout({
76
- * sidebarWidth: 400,
77
- * });
78
- *
79
- * useEffect(() => {
80
- * applyLayout(displayMode);
81
- * }, [displayMode]);
82
- * ```
83
- */
84
- export function useChatLayout(config?: ChatLayoutConfig): UseChatLayoutReturn {
85
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
86
- const { initialWidth, animationDuration, pushTarget } = mergedConfig;
87
-
88
- // Sidebar width with localStorage persistence
89
- const [storedWidth, setStoredWidth] = useLocalStorage<number>(storageKeys.sidebarWidth, initialWidth);
90
-
91
- // Clamp stored width to valid range
92
- const sidebarWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, storedWidth));
93
- const sidebarWidthRef = useRef(sidebarWidth);
94
-
95
- // Resizing state (runtime only, not persisted)
96
- const [isResizing, setIsResizing] = useState(false);
97
-
98
- // Keep ref in sync
99
- useEffect(() => {
100
- sidebarWidthRef.current = sidebarWidth;
101
- }, [sidebarWidth]);
102
-
103
- // Store original styles for cleanup
104
- const originalStylesRef = useRef<{
105
- marginRight?: string;
106
- overflowX?: string;
107
- transition?: string;
108
- } | null>(null);
109
-
110
- // Store original styles for fixed elements
111
- const fixedElementsRef = useRef<FixedElementOriginalStyles[]>([]);
112
-
113
- // Current mode for cleanup
114
- const currentModeRef = useRef<ChatDisplayMode>('closed');
115
-
116
- /**
117
- * Get the target element to push
118
- */
119
- const getTargetElement = useCallback((): HTMLElement | null => {
120
- if (typeof window === 'undefined') return null;
121
-
122
- if (pushTarget === 'body') {
123
- return document.body;
124
- } else if (pushTarget === 'main') {
125
- return document.querySelector('main');
126
- } else {
127
- return document.querySelector(pushTarget);
128
- }
129
- }, [pushTarget]);
130
-
131
- /**
132
- * Find all fixed/sticky elements that need right adjustment
133
- */
134
- const getFixedElements = useCallback((): HTMLElement[] => {
135
- if (typeof window === 'undefined') return [];
136
-
137
- const elements: HTMLElement[] = [];
138
- const allElements = document.querySelectorAll('*');
139
-
140
- allElements.forEach((el) => {
141
- if (!(el instanceof HTMLElement)) return;
142
- // Skip chat sidebar itself
143
- if (el.closest('[data-chat-sidebar-panel]')) return;
144
-
145
- const style = window.getComputedStyle(el);
146
- const position = style.position;
147
- const right = style.right;
148
-
149
- // Check for fixed/sticky elements with right: 0
150
- if ((position === 'fixed' || position === 'sticky') && right === '0px') {
151
- elements.push(el);
152
- }
153
- });
154
-
155
- return elements;
156
- }, []);
157
-
158
- /**
159
- * Save original styles before modification
160
- */
161
- const saveOriginalStyles = useCallback((element: HTMLElement) => {
162
- if (!originalStylesRef.current) {
163
- originalStylesRef.current = {
164
- marginRight: element.style.marginRight,
165
- overflowX: element.style.overflowX,
166
- transition: element.style.transition,
167
- };
168
- }
169
- }, []);
170
-
171
- /**
172
- * Restore original styles
173
- */
174
- const restoreOriginalStyles = useCallback((element: HTMLElement) => {
175
- if (originalStylesRef.current) {
176
- element.style.marginRight = originalStylesRef.current.marginRight || '';
177
- element.style.overflowX = originalStylesRef.current.overflowX || '';
178
- element.style.transition = originalStylesRef.current.transition || '';
179
- element.removeAttribute('data-chat-sidebar');
180
- originalStylesRef.current = null;
181
- }
182
- }, []);
183
-
184
- /**
185
- * Adjust fixed elements for sidebar
186
- */
187
- const adjustFixedElements = useCallback(
188
- (open: boolean) => {
189
- const currentWidth = sidebarWidthRef.current;
190
- if (open) {
191
- // Save and adjust fixed elements
192
- const fixedElements = getFixedElements();
193
- fixedElementsRef.current = fixedElements.map((el) => ({
194
- element: el,
195
- right: el.style.right,
196
- transition: el.style.transition,
197
- }));
198
-
199
- fixedElements.forEach((el) => {
200
- el.style.transition = `right ${animationDuration}ms ease`;
201
- el.style.right = `${currentWidth}px`;
202
- });
203
- } else {
204
- // Restore fixed elements
205
- fixedElementsRef.current.forEach(({ element, right, transition }) => {
206
- element.style.transition = `right ${animationDuration}ms ease`;
207
- element.style.right = '0px';
208
-
209
- // Restore original after animation
210
- setTimeout(() => {
211
- element.style.right = right;
212
- element.style.transition = transition;
213
- }, animationDuration);
214
- });
215
- fixedElementsRef.current = [];
216
- }
217
- },
218
- [getFixedElements, animationDuration]
219
- );
220
-
221
- /**
222
- * Apply sidebar mode layout
223
- */
224
- const applySidebarLayout = useCallback(() => {
225
- const target = getTargetElement();
226
- if (!target) return;
227
-
228
- const currentWidth = sidebarWidthRef.current;
229
-
230
- saveOriginalStyles(target);
231
-
232
- // Add smooth transition
233
- target.style.transition = `margin-right ${animationDuration}ms ease`;
234
- target.style.marginRight = `${currentWidth}px`;
235
- target.style.overflowX = 'hidden';
236
- target.setAttribute('data-chat-sidebar', 'open');
237
-
238
- // Adjust fixed elements (header, etc.)
239
- adjustFixedElements(true);
240
-
241
- currentModeRef.current = 'sidebar';
242
- }, [getTargetElement, saveOriginalStyles, animationDuration, adjustFixedElements]);
243
-
244
- /**
245
- * Apply floating/closed mode layout (reset sidebar push)
246
- */
247
- const applyDefaultLayout = useCallback(
248
- (mode: ChatDisplayMode) => {
249
- const target = getTargetElement();
250
- if (!target) return;
251
-
252
- // Only restore if we were in sidebar mode
253
- if (currentModeRef.current === 'sidebar') {
254
- // Add transition for smooth animation
255
- target.style.transition = `margin-right ${animationDuration}ms ease`;
256
- target.style.marginRight = '0px';
257
-
258
- // Restore fixed elements
259
- adjustFixedElements(false);
260
-
261
- // Remove styles after animation completes
262
- setTimeout(() => {
263
- restoreOriginalStyles(target);
264
- }, animationDuration);
265
- }
266
-
267
- currentModeRef.current = mode;
268
- },
269
- [getTargetElement, restoreOriginalStyles, animationDuration, adjustFixedElements]
270
- );
271
-
272
- /**
273
- * Apply layout changes for given mode
274
- */
275
- const applyLayout = useCallback(
276
- (mode: ChatDisplayMode) => {
277
- if (mode === 'sidebar') {
278
- applySidebarLayout();
279
- } else {
280
- applyDefaultLayout(mode);
281
- }
282
- },
283
- [applySidebarLayout, applyDefaultLayout]
284
- );
285
-
286
- /**
287
- * Reset layout to default (cleanup)
288
- */
289
- const resetLayout = useCallback(() => {
290
- const target = getTargetElement();
291
- if (target && originalStylesRef.current) {
292
- restoreOriginalStyles(target);
293
- }
294
- // Restore fixed elements immediately on cleanup
295
- fixedElementsRef.current.forEach(({ element, right, transition }) => {
296
- element.style.right = right;
297
- element.style.transition = transition;
298
- });
299
- fixedElementsRef.current = [];
300
- currentModeRef.current = 'closed';
301
- }, [getTargetElement, restoreOriginalStyles]);
302
-
303
- /**
304
- * Update width during resize (no animation)
305
- */
306
- const updateWidthImmediate = useCallback(
307
- (newWidth: number) => {
308
- const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, newWidth));
309
-
310
- // Update body margin
311
- const target = getTargetElement();
312
- if (target && currentModeRef.current === 'sidebar') {
313
- target.style.transition = 'none';
314
- target.style.marginRight = `${clampedWidth}px`;
315
- }
316
-
317
- // Update fixed elements
318
- fixedElementsRef.current.forEach(({ element }) => {
319
- element.style.transition = 'none';
320
- element.style.right = `${clampedWidth}px`;
321
- });
322
-
323
- return clampedWidth;
324
- },
325
- [getTargetElement]
326
- );
327
-
328
- /**
329
- * Update sidebar width (for resize)
330
- */
331
- const updateWidth = useCallback(
332
- (newWidth: number) => {
333
- const clampedWidth = updateWidthImmediate(newWidth);
334
- setStoredWidth(clampedWidth);
335
- },
336
- [updateWidthImmediate, setStoredWidth]
337
- );
338
-
339
- /**
340
- * Start resize operation
341
- */
342
- const startResize = useCallback(
343
- (e: React.MouseEvent) => {
344
- e.preventDefault();
345
- setIsResizing(true);
346
-
347
- const startX = e.clientX;
348
- const startWidth = sidebarWidthRef.current;
349
-
350
- const handleMouseMove = (moveEvent: MouseEvent) => {
351
- // Calculate new width (dragging left increases width)
352
- const deltaX = startX - moveEvent.clientX;
353
- const newWidth = startWidth + deltaX;
354
- const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, newWidth));
355
-
356
- // Update DOM immediately for smooth feel
357
- updateWidthImmediate(clampedWidth);
358
- sidebarWidthRef.current = clampedWidth;
359
-
360
- // Also update React state so sidebar visually resizes
361
- setStoredWidth(clampedWidth);
362
- };
363
-
364
- const handleMouseUp = () => {
365
- setIsResizing(false);
366
- document.removeEventListener('mousemove', handleMouseMove);
367
- document.removeEventListener('mouseup', handleMouseUp);
368
- document.body.style.cursor = '';
369
- document.body.style.userSelect = '';
370
- };
371
-
372
- document.addEventListener('mousemove', handleMouseMove);
373
- document.addEventListener('mouseup', handleMouseUp);
374
- document.body.style.cursor = 'ew-resize';
375
- document.body.style.userSelect = 'none';
376
- },
377
- [updateWidthImmediate, setStoredWidth]
378
- );
379
-
380
- /**
381
- * Get CSS styles for sidebar container
382
- */
383
- const getSidebarStyles = useCallback((): React.CSSProperties => {
384
- return {
385
- position: 'fixed',
386
- top: 0,
387
- right: 0,
388
- bottom: 0,
389
- width: `${sidebarWidth}px`,
390
- zIndex: sidebarConfig.zIndex,
391
- };
392
- }, [sidebarWidth]);
393
-
394
- /**
395
- * Get CSS styles for floating container
396
- */
397
- const getFloatingStyles = useCallback(
398
- (position: 'bottom-right' | 'bottom-left'): React.CSSProperties => {
399
- return {
400
- position: 'fixed',
401
- zIndex: sidebarConfig.zIndex - 50,
402
- bottom: fabConfig.bottom,
403
- ...(position === 'bottom-right' ? { right: fabConfig.right } : { left: fabConfig.right }),
404
- };
405
- },
406
- []
407
- );
408
-
409
- /**
410
- * Get CSS styles for FAB button
411
- */
412
- const getFabStyles = useCallback(
413
- (position: 'bottom-right' | 'bottom-left'): React.CSSProperties => {
414
- return {
415
- position: 'fixed',
416
- zIndex: sidebarConfig.zIndex - 50,
417
- bottom: fabConfig.bottom,
418
- ...(position === 'bottom-right' ? { right: fabConfig.right } : { left: fabConfig.right }),
419
- };
420
- },
421
- []
422
- );
423
-
424
- // Cleanup on unmount
425
- useEffect(() => {
426
- return () => {
427
- resetLayout();
428
- };
429
- }, [resetLayout]);
430
-
431
- return {
432
- sidebarWidth,
433
- applyLayout,
434
- resetLayout,
435
- updateWidth,
436
- startResize,
437
- isResizing,
438
- getSidebarStyles,
439
- getFloatingStyles,
440
- getFabStyles,
441
- };
442
- }
@@ -1,90 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback } from 'react';
4
-
5
- import type { McpChatEventDetail, UseMcpChatReturn } from '../types';
6
-
7
- /**
8
- * Hook to send messages to MCP Chat from anywhere in the app
9
- *
10
- * @example
11
- * ```tsx
12
- * function ErrorBoundary({ error }) {
13
- * const { sendToChat } = useMcpChat();
14
- *
15
- * const explainError = () => {
16
- * sendToChat({
17
- * message: `Explain this error: ${error.message}`,
18
- * context: {
19
- * type: 'error',
20
- * data: { error: error.stack },
21
- * source: 'ErrorBoundary'
22
- * }
23
- * });
24
- * };
25
- *
26
- * return <button onClick={explainError}>Explain Error</button>;
27
- * }
28
- * ```
29
- */
30
- export function useMcpChat(): UseMcpChatReturn {
31
- /**
32
- * Send message to chat via CustomEvent
33
- */
34
- const sendToChat = useCallback((detail: McpChatEventDetail) => {
35
- if (typeof window === 'undefined') {
36
- console.error('[useMcpChat] Cannot send message: window is not available');
37
- return;
38
- }
39
-
40
- // Create custom event
41
- const event = new CustomEvent('mcp:chat:send', {
42
- detail,
43
- bubbles: true,
44
- });
45
-
46
- // Set up handler confirmation listener
47
- let handled = false;
48
- const handleConfirmation = () => {
49
- handled = true;
50
- };
51
-
52
- window.addEventListener('mcp:chat:handled', handleConfirmation, { once: true });
53
-
54
- // Dispatch event
55
- window.dispatchEvent(event);
56
-
57
- // Check if event was handled
58
- setTimeout(() => {
59
- window.removeEventListener('mcp:chat:handled', handleConfirmation);
60
-
61
- if (!handled) {
62
- const errorMessage = 'AI Chat is not available. Please make sure the chat component is loaded.';
63
- console.error('[useMcpChat]', errorMessage);
64
-
65
- // Import consola dynamically if available
66
- if (typeof window !== 'undefined' && (window as any).consola) {
67
- (window as any).consola.error('[useMcpChat] Chat not available');
68
- }
69
-
70
- // Show user-friendly alert
71
- alert(errorMessage);
72
- }
73
- }, 100);
74
- }, []);
75
-
76
- /**
77
- * Check if chat is available
78
- */
79
- const isChatAvailable = useCallback(() => {
80
- if (typeof window === 'undefined') return false;
81
-
82
- // Check if chat registered itself
83
- return (window as any).__MCP_CHAT_AVAILABLE__ === true;
84
- }, []);
85
-
86
- return {
87
- sendToChat,
88
- isChatAvailable,
89
- };
90
- }
@@ -1,79 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * @djangocfg/mcp-chat
5
- *
6
- * React chat components for DjangoCFG documentation assistant.
7
- * Works with @djangocfg/mcp for semantic search.
8
- *
9
- * @example Basic usage (standalone widget)
10
- * ```tsx
11
- * import { ChatWidget } from '@djangocfg/mcp-chat';
12
- *
13
- * export default function Layout({ children }) {
14
- * return (
15
- * <>
16
- * {children}
17
- * <ChatWidget apiEndpoint="/api/chat" />
18
- * </>
19
- * );
20
- * }
21
- * ```
22
- *
23
- * @example With provider (for accessing chat from multiple components)
24
- * ```tsx
25
- * import { ChatProvider, ChatWidget, useChatContext } from '@djangocfg/mcp-chat';
26
- *
27
- * function OpenChatButton() {
28
- * const { openChat } = useChatContext();
29
- * return <button onClick={openChat}>Ask AI</button>;
30
- * }
31
- *
32
- * export default function Layout({ children }) {
33
- * return (
34
- * <ChatProvider apiEndpoint="/api/chat">
35
- * {children}
36
- * <OpenChatButton />
37
- * <ChatWidget />
38
- * </ChatProvider>
39
- * );
40
- * }
41
- * ```
42
- */
43
-
44
- // Components
45
- export { ChatWidget, AIChatWidget, ChatPanel, MessageBubble, AIMessageInput, AskAIButton } from './components';
46
- export type {
47
- ChatWidgetProps,
48
- AIChatWidgetProps,
49
- MessageBubbleProps,
50
- AIMessageInputProps,
51
- AskAIButtonProps,
52
- } from './components';
53
-
54
- // Context
55
- export { AIChatProvider, useAIChatContext, useAIChatContextOptional } from './context';
56
- export type {
57
- AIChatContextState,
58
- AIChatContextActions,
59
- AIChatContextValue,
60
- AIChatProviderProps,
61
- } from './context';
62
-
63
- // Hooks
64
- export { useAIChat, useChatLayout, useMcpChat } from './hooks';
65
- export type { ChatLayoutConfig, UseChatLayoutReturn } from './hooks';
66
-
67
- // Types
68
- export type {
69
- AIChatMessage,
70
- AIChatSource,
71
- AIMessageRole,
72
- AIChatApiResponse,
73
- UseAIChatOptions,
74
- UseAIChatReturn,
75
- ChatWidgetConfig,
76
- McpChatContextType,
77
- McpChatEventDetail,
78
- UseMcpChatReturn,
79
- } from './types';