@djangocfg/ui-nextjs 1.4.45

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/package.json +110 -0
  4. package/src/animations/AnimatedBackground.tsx +645 -0
  5. package/src/animations/index.ts +2 -0
  6. package/src/blocks/ArticleCard.tsx +94 -0
  7. package/src/blocks/ArticleList.tsx +95 -0
  8. package/src/blocks/CTASection.tsx +136 -0
  9. package/src/blocks/FeatureSection.tsx +104 -0
  10. package/src/blocks/Hero.tsx +102 -0
  11. package/src/blocks/NewsletterSection.tsx +119 -0
  12. package/src/blocks/StatsSection.tsx +103 -0
  13. package/src/blocks/SuperHero.tsx +328 -0
  14. package/src/blocks/TestimonialSection.tsx +122 -0
  15. package/src/blocks/index.ts +9 -0
  16. package/src/components/README.md +2018 -0
  17. package/src/components/breadcrumb-navigation.tsx +127 -0
  18. package/src/components/breadcrumb.tsx +132 -0
  19. package/src/components/button-download.tsx +275 -0
  20. package/src/components/dropdown-menu.tsx +219 -0
  21. package/src/components/index.ts +86 -0
  22. package/src/components/markdown/MarkdownMessage.tsx +338 -0
  23. package/src/components/markdown/index.ts +5 -0
  24. package/src/components/menubar.tsx +274 -0
  25. package/src/components/multi-select-pro/async.tsx +608 -0
  26. package/src/components/multi-select-pro/helpers.tsx +84 -0
  27. package/src/components/multi-select-pro/index.tsx +622 -0
  28. package/src/components/navigation-menu.tsx +153 -0
  29. package/src/components/pagination-static.tsx +348 -0
  30. package/src/components/pagination.tsx +138 -0
  31. package/src/components/phone-input.tsx +276 -0
  32. package/src/components/sidebar.tsx +866 -0
  33. package/src/components/sonner.tsx +31 -0
  34. package/src/components/ssr-pagination.tsx +237 -0
  35. package/src/hooks/index.ts +19 -0
  36. package/src/hooks/useCfgRouter.ts +153 -0
  37. package/src/hooks/useLocalStorage.ts +221 -0
  38. package/src/hooks/useQueryParams.ts +73 -0
  39. package/src/hooks/useSessionStorage.ts +188 -0
  40. package/src/hooks/useTheme.ts +57 -0
  41. package/src/index.ts +24 -0
  42. package/src/lib/index.ts +2 -0
  43. package/src/styles/index.css +2 -0
  44. package/src/theme/ForceTheme.tsx +115 -0
  45. package/src/theme/ThemeProvider.tsx +82 -0
  46. package/src/theme/ThemeToggle.tsx +52 -0
  47. package/src/theme/index.ts +3 -0
  48. package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
  49. package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
  50. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
  51. package/src/tools/JsonForm/index.ts +46 -0
  52. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
  53. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
  54. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
  55. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
  56. package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
  57. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
  58. package/src/tools/JsonForm/templates/index.ts +12 -0
  59. package/src/tools/JsonForm/types.ts +83 -0
  60. package/src/tools/JsonForm/utils.ts +212 -0
  61. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
  62. package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
  63. package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
  64. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
  65. package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
  66. package/src/tools/JsonForm/widgets/index.ts +12 -0
  67. package/src/tools/JsonTree/index.tsx +252 -0
  68. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
  69. package/src/tools/LottiePlayer/index.tsx +54 -0
  70. package/src/tools/LottiePlayer/types.ts +108 -0
  71. package/src/tools/LottiePlayer/useLottie.ts +163 -0
  72. package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
  73. package/src/tools/Mermaid/index.tsx +40 -0
  74. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
  75. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
  76. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
  77. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
  78. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
  79. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  80. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
  81. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
  82. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  83. package/src/tools/OpenapiViewer/constants.ts +39 -0
  84. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
  85. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  86. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  87. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
  88. package/src/tools/OpenapiViewer/index.tsx +36 -0
  89. package/src/tools/OpenapiViewer/types.ts +152 -0
  90. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  91. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  92. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  93. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  94. package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
  95. package/src/tools/PrettyCode/index.tsx +43 -0
  96. package/src/tools/VideoPlayer/README.md +239 -0
  97. package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
  98. package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
  99. package/src/tools/VideoPlayer/index.ts +9 -0
  100. package/src/tools/VideoPlayer/types.ts +62 -0
  101. package/src/tools/index.ts +43 -0
@@ -0,0 +1,219 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { cn } from "@djangocfg/ui-core/lib"
6
+ import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
7
+ import Link from "next/link"
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
31
+ inset && "pl-8",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRightIcon className="ml-auto" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ))
40
+ DropdownMenuSubTrigger.displayName =
41
+ DropdownMenuPrimitive.SubTrigger.displayName
42
+
43
+ const DropdownMenuSubContent = React.forwardRef<
44
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
45
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
46
+ >(({ className, ...props }, ref) => (
47
+ <DropdownMenuPrimitive.SubContent
48
+ ref={ref}
49
+ className={cn(
50
+ "z-600 min-w-32 overflow-hidden rounded-sm border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ ))
56
+ DropdownMenuSubContent.displayName =
57
+ DropdownMenuPrimitive.SubContent.displayName
58
+
59
+ const DropdownMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
62
+ >(({ className, sideOffset = 4, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Portal>
64
+ <DropdownMenuPrimitive.Content
65
+ ref={ref}
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "z-600 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-32 overflow-y-auto overflow-x-hidden rounded-sm border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-sm",
69
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
70
+ className
71
+ )}
72
+ {...props}
73
+ />
74
+ </DropdownMenuPrimitive.Portal>
75
+ ))
76
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
77
+
78
+ const DropdownMenuItem = React.forwardRef<
79
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
80
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
81
+ inset?: boolean
82
+ href?: string
83
+ key?: React.Key
84
+ }
85
+ >(({ className, inset, href, children, ...props }, ref) => {
86
+ const classes = cn(
87
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
88
+ inset && "pl-8",
89
+ className
90
+ )
91
+
92
+ if (href) {
93
+ return (
94
+ <DropdownMenuPrimitive.Item asChild ref={ref} {...props}>
95
+ <Link href={href} className={classes}>
96
+ {children}
97
+ </Link>
98
+ </DropdownMenuPrimitive.Item>
99
+ )
100
+ }
101
+
102
+ return (
103
+ <DropdownMenuPrimitive.Item
104
+ ref={ref}
105
+ className={classes}
106
+ {...props}
107
+ >
108
+ {children}
109
+ </DropdownMenuPrimitive.Item>
110
+ )
111
+ })
112
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
113
+
114
+ const DropdownMenuCheckboxItem = React.forwardRef<
115
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
116
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> & { key?: React.Key }
117
+ >(({ className, children, checked, ...props }, ref) => (
118
+ <DropdownMenuPrimitive.CheckboxItem
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ checked={checked}
125
+ {...props}
126
+ >
127
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
128
+ <DropdownMenuPrimitive.ItemIndicator>
129
+ <CheckIcon className="h-4 w-4" />
130
+ </DropdownMenuPrimitive.ItemIndicator>
131
+ </span>
132
+ {children}
133
+ </DropdownMenuPrimitive.CheckboxItem>
134
+ ))
135
+ DropdownMenuCheckboxItem.displayName =
136
+ DropdownMenuPrimitive.CheckboxItem.displayName
137
+
138
+ const DropdownMenuRadioItem = React.forwardRef<
139
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
140
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> & { key?: React.Key }
141
+ >(({ className, children, ...props }, ref) => (
142
+ <DropdownMenuPrimitive.RadioItem
143
+ ref={ref}
144
+ className={cn(
145
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
146
+ className
147
+ )}
148
+ {...props}
149
+ >
150
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
151
+ <DropdownMenuPrimitive.ItemIndicator>
152
+ <DotFilledIcon className="h-2 w-2 fill-current" />
153
+ </DropdownMenuPrimitive.ItemIndicator>
154
+ </span>
155
+ {children}
156
+ </DropdownMenuPrimitive.RadioItem>
157
+ ))
158
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
159
+
160
+ const DropdownMenuLabel = React.forwardRef<
161
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
162
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
163
+ inset?: boolean
164
+ }
165
+ >(({ className, inset, ...props }, ref) => (
166
+ <DropdownMenuPrimitive.Label
167
+ ref={ref}
168
+ className={cn(
169
+ "px-2 py-1.5 text-sm font-semibold",
170
+ inset && "pl-8",
171
+ className
172
+ )}
173
+ {...props}
174
+ />
175
+ ))
176
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
177
+
178
+ const DropdownMenuSeparator = React.forwardRef<
179
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
180
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
181
+ >(({ className, ...props }, ref) => (
182
+ <DropdownMenuPrimitive.Separator
183
+ ref={ref}
184
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
185
+ {...props}
186
+ />
187
+ ))
188
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
189
+
190
+ const DropdownMenuShortcut = ({
191
+ className,
192
+ ...props
193
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
194
+ return (
195
+ <span
196
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
197
+ {...props}
198
+ />
199
+ )
200
+ }
201
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
202
+
203
+ export {
204
+ DropdownMenu,
205
+ DropdownMenuTrigger,
206
+ DropdownMenuContent,
207
+ DropdownMenuItem,
208
+ DropdownMenuCheckboxItem,
209
+ DropdownMenuRadioItem,
210
+ DropdownMenuLabel,
211
+ DropdownMenuSeparator,
212
+ DropdownMenuShortcut,
213
+ DropdownMenuGroup,
214
+ DropdownMenuPortal,
215
+ DropdownMenuSub,
216
+ DropdownMenuSubContent,
217
+ DropdownMenuSubTrigger,
218
+ DropdownMenuRadioGroup,
219
+ }
@@ -0,0 +1,86 @@
1
+ // ============================================================================
2
+ // Re-export all components from @djangocfg/ui-core
3
+ // ============================================================================
4
+ export * from '@djangocfg/ui-core/components';
5
+
6
+ // ============================================================================
7
+ // Next.js specific components (use next/link, next/image, etc.)
8
+ // ============================================================================
9
+
10
+ // Navigation Components (Next.js)
11
+ export { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, navigationMenuTriggerStyle } from './navigation-menu';
12
+ export { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis } from './breadcrumb';
13
+ export { BreadcrumbNavigation } from './breadcrumb-navigation';
14
+ export type { BreadcrumbItem as BreadcrumbNavigationItem, BreadcrumbNavigationProps } from './breadcrumb-navigation';
15
+ export { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarShortcut, MenubarTrigger } from './menubar';
16
+
17
+ // Interactive Components (Next.js)
18
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from './dropdown-menu';
19
+
20
+ // Pagination (Next.js)
21
+ export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from './pagination';
22
+ export { SSRPagination } from './ssr-pagination';
23
+ export { StaticPagination, useDRFPaginationInfo, useDRFPagination } from './pagination-static';
24
+ export type { DRFPaginatedResponse } from './pagination-static';
25
+
26
+ // Phone Input (uses DropdownMenu)
27
+ export { PhoneInput } from './phone-input';
28
+
29
+ // Sidebar (Next.js)
30
+ export {
31
+ Sidebar,
32
+ SidebarContent,
33
+ SidebarFooter,
34
+ SidebarGroup,
35
+ SidebarGroupAction,
36
+ SidebarGroupContent,
37
+ SidebarGroupLabel,
38
+ SidebarHeader,
39
+ SidebarInput,
40
+ SidebarInset,
41
+ SidebarMenu,
42
+ SidebarMenuAction,
43
+ SidebarMenuBadge,
44
+ SidebarMenuButton,
45
+ SidebarMenuItem,
46
+ SidebarMenuSkeleton,
47
+ SidebarMenuSub,
48
+ SidebarMenuSubButton,
49
+ SidebarMenuSubItem,
50
+ SidebarProvider,
51
+ SidebarRail,
52
+ SidebarSeparator,
53
+ SidebarTrigger,
54
+ useSidebar,
55
+ } from './sidebar';
56
+
57
+ // Download Button (uses localStorage for auth token)
58
+ export { DownloadButton } from './button-download';
59
+ export type { DownloadButtonProps } from './button-download';
60
+
61
+ // Sonner Toast (Next.js specific)
62
+ export { Toaster as SonnerToaster } from './sonner';
63
+
64
+ // MultiSelect Pro (advanced component)
65
+ export { MultiSelectPro } from './multi-select-pro';
66
+ export type {
67
+ MultiSelectProOption,
68
+ MultiSelectProProps,
69
+ MultiSelectProGroup,
70
+ MultiSelectProRef,
71
+ AnimationConfig,
72
+ ResponsiveConfig
73
+ } from './multi-select-pro';
74
+ export { MultiSelectProAsync } from './multi-select-pro/async';
75
+ export type {
76
+ MultiSelectProAsyncOption,
77
+ MultiSelectProAsyncProps,
78
+ MultiSelectProAsyncGroup,
79
+ MultiSelectProAsyncRef,
80
+ } from './multi-select-pro/async';
81
+ export { createOption, createOptions } from './multi-select-pro/helpers';
82
+ export type { OptionBuilderConfig } from './multi-select-pro/helpers';
83
+
84
+ // Markdown Components
85
+ export { MarkdownMessage } from './markdown';
86
+ export type { MarkdownMessageProps } from './markdown';
@@ -0,0 +1,338 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import ReactMarkdown from 'react-markdown';
5
+ import remarkGfm from 'remark-gfm';
6
+ import Mermaid from '../../tools/Mermaid';
7
+ import PrettyCode from '../../tools/PrettyCode';
8
+ import { Button } from '@djangocfg/ui-core/components';
9
+ import { useCopy } from '@djangocfg/ui-core/hooks';
10
+ import { useTheme } from '../../hooks/useTheme';
11
+ import { Copy } from 'lucide-react';
12
+ import type { Components } from 'react-markdown';
13
+
14
+ // Helper function to extract text content from React children
15
+ const extractTextFromChildren = (children: React.ReactNode): string => {
16
+ if (typeof children === 'string') {
17
+ return children;
18
+ }
19
+
20
+ if (typeof children === 'number') {
21
+ return String(children);
22
+ }
23
+
24
+ if (React.isValidElement(children)) {
25
+ const props = children.props as { children?: React.ReactNode };
26
+ return extractTextFromChildren(props.children);
27
+ }
28
+
29
+ if (Array.isArray(children)) {
30
+ return children.map(extractTextFromChildren).join('');
31
+ }
32
+
33
+ return '';
34
+ };
35
+
36
+ export interface MarkdownMessageProps {
37
+ /** Markdown content to render */
38
+ content: string;
39
+ /** Additional CSS classes */
40
+ className?: string;
41
+ /** Whether the message is from the user (affects styling) */
42
+ isUser?: boolean;
43
+ }
44
+
45
+ // Code block component with copy functionality
46
+ interface CodeBlockProps {
47
+ code: string;
48
+ language: string;
49
+ isUser: boolean;
50
+ }
51
+
52
+ const CodeBlock: React.FC<CodeBlockProps> = ({ code, language, isUser }) => {
53
+ const { copyToClipboard } = useCopy();
54
+ const theme = useTheme();
55
+
56
+ const handleCopy = () => {
57
+ copyToClipboard(code, "Code copied to clipboard!");
58
+ };
59
+
60
+ return (
61
+ <div className="relative group my-3">
62
+ {/* Copy button */}
63
+ <Button
64
+ variant="ghost"
65
+ size="sm"
66
+ onClick={handleCopy}
67
+ className={`
68
+ absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
69
+ h-8 w-8 p-0
70
+ ${isUser
71
+ ? 'hover:bg-white/20 text-white'
72
+ : 'hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground'
73
+ }
74
+ `}
75
+ title="Copy code"
76
+ >
77
+ <Copy className="h-4 w-4" />
78
+ </Button>
79
+
80
+ {/* Code content */}
81
+ <PrettyCode
82
+ data={code}
83
+ language={language}
84
+ className="text-xs"
85
+ customBg={isUser ? "bg-white/10" : "bg-muted dark:bg-muted"}
86
+ mode={theme}
87
+ />
88
+ </div>
89
+ );
90
+ };
91
+
92
+ // Custom components for markdown in chat
93
+ // Base size: text-sm (14px) for consistency
94
+ const createMarkdownComponents = (isUser: boolean = false): Components => ({
95
+ // Headings - scaled for chat context
96
+ h1: ({ children }) => (
97
+ <h1 className="text-base font-bold mb-2 mt-3 first:mt-0">{children}</h1>
98
+ ),
99
+ h2: ({ children }) => (
100
+ <h2 className="text-sm font-bold mb-2 mt-3 first:mt-0">{children}</h2>
101
+ ),
102
+ h3: ({ children }) => (
103
+ <h3 className="text-sm font-semibold mb-1 mt-2 first:mt-0">{children}</h3>
104
+ ),
105
+ h4: ({ children }) => (
106
+ <h4 className="text-sm font-semibold mb-1 mt-2 first:mt-0">{children}</h4>
107
+ ),
108
+ h5: ({ children }) => (
109
+ <h5 className="text-sm font-medium mb-1 mt-2 first:mt-0">{children}</h5>
110
+ ),
111
+ h6: ({ children }) => (
112
+ <h6 className="text-sm font-medium mb-1 mt-2 first:mt-0">{children}</h6>
113
+ ),
114
+
115
+ // Paragraphs - compact spacing for chat
116
+ p: ({ children }) => (
117
+ <p className="text-sm mb-2 last:mb-0 leading-relaxed break-words">{children}</p>
118
+ ),
119
+
120
+ // Lists - compact
121
+ ul: ({ children }) => (
122
+ <ul className="list-disc list-inside mb-2 space-y-1 text-sm">{children}</ul>
123
+ ),
124
+ ol: ({ children }) => (
125
+ <ol className="list-decimal list-inside mb-2 space-y-1 text-sm">{children}</ol>
126
+ ),
127
+ li: ({ children }) => (
128
+ <li className="break-words">{children}</li>
129
+ ),
130
+
131
+ // Links - appropriate for chat context
132
+ a: ({ href, children }) => (
133
+ <a
134
+ href={href}
135
+ className="text-sm text-primary underline hover:text-primary/80 transition-colors break-all"
136
+ target={href?.startsWith('http') ? '_blank' : undefined}
137
+ rel={href?.startsWith('http') ? 'noopener noreferrer' : undefined}
138
+ >
139
+ {children}
140
+ </a>
141
+ ),
142
+
143
+ // Code blocks - using CodeBlock component with copy functionality
144
+ pre: ({ children }) => {
145
+ // Extract code content and language
146
+ let codeContent = '';
147
+ let language = 'plaintext';
148
+
149
+ if (React.isValidElement(children)) {
150
+ const child = children;
151
+
152
+ if (child.type === 'code' || (typeof child.type === 'function' && child.type.name === 'code')) {
153
+ const codeProps = child.props as { className?: string; children?: React.ReactNode };
154
+ const rawClassName = codeProps.className;
155
+ language = rawClassName?.replace(/language-/, '').trim() || 'plaintext';
156
+ codeContent = extractTextFromChildren(codeProps.children).trim();
157
+ } else {
158
+ codeContent = extractTextFromChildren(children).trim();
159
+ }
160
+ } else {
161
+ codeContent = extractTextFromChildren(children).trim();
162
+ }
163
+
164
+ // If still no content, show placeholder
165
+ if (!codeContent) {
166
+ return (
167
+ <div className="my-3 p-3 bg-muted rounded text-sm text-muted-foreground">
168
+ No content available
169
+ </div>
170
+ );
171
+ }
172
+
173
+ // Handle Mermaid diagrams separately
174
+ if (language === 'mermaid') {
175
+ return (
176
+ <div className="my-3 max-w-full overflow-x-auto">
177
+ <Mermaid chart={codeContent} className="max-w-[600px] mx-auto" />
178
+ </div>
179
+ );
180
+ }
181
+
182
+ // Try to use CodeBlock component, fallback to simple pre if it fails
183
+ try {
184
+ return <CodeBlock code={codeContent} language={language} isUser={isUser} />;
185
+ } catch (error) {
186
+ // Fallback to simple pre element with copy button
187
+ console.warn('CodeBlock failed, using fallback:', error);
188
+ return (
189
+ <div className="relative group my-3">
190
+ <Button
191
+ variant="ghost"
192
+ size="sm"
193
+ onClick={() => {
194
+ navigator.clipboard.writeText(codeContent);
195
+ }}
196
+ className={`
197
+ absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
198
+ h-8 w-8 p-0
199
+ ${isUser
200
+ ? 'hover:bg-white/20 text-white'
201
+ : 'hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground'
202
+ }
203
+ `}
204
+ title="Copy code"
205
+ >
206
+ <Copy className="h-4 w-4" />
207
+ </Button>
208
+ <pre className={`
209
+ p-3 rounded text-xs font-mono overflow-x-auto
210
+ ${isUser
211
+ ? 'bg-white/10 text-white'
212
+ : 'bg-muted text-foreground'
213
+ }
214
+ `}>
215
+ <code>{codeContent}</code>
216
+ </pre>
217
+ </div>
218
+ );
219
+ }
220
+ },
221
+
222
+ // Inline code
223
+ code: ({ children, className }) => {
224
+ // If it's inside a pre tag, let pre handle it
225
+ if (className?.includes('language-')) {
226
+ return <code className={className}>{children}</code>;
227
+ }
228
+
229
+ // Extract text content safely
230
+ const codeContent = extractTextFromChildren(children);
231
+
232
+ // Inline code styling
233
+ return (
234
+ <code className="px-1.5 py-0.5 rounded text-xs font-mono bg-muted text-foreground break-all">
235
+ {codeContent}
236
+ </code>
237
+ );
238
+ },
239
+
240
+ // Blockquotes
241
+ blockquote: ({ children }) => (
242
+ <blockquote className="text-sm border-l-2 border-border pl-3 my-2 italic text-muted-foreground break-words">
243
+ {children}
244
+ </blockquote>
245
+ ),
246
+
247
+ // Tables - compact for chat
248
+ table: ({ children }) => (
249
+ <div className="overflow-x-auto my-3">
250
+ <table className="min-w-full text-sm border-collapse">
251
+ {children}
252
+ </table>
253
+ </div>
254
+ ),
255
+ thead: ({ children }) => (
256
+ <thead className="bg-muted/50">
257
+ {children}
258
+ </thead>
259
+ ),
260
+ tbody: ({ children }) => (
261
+ <tbody>{children}</tbody>
262
+ ),
263
+ tr: ({ children }) => (
264
+ <tr className="border-b border-border/50">{children}</tr>
265
+ ),
266
+ th: ({ children }) => (
267
+ <th className="px-2 py-1 text-left font-medium break-words">{children}</th>
268
+ ),
269
+ td: ({ children }) => (
270
+ <td className="px-2 py-1 break-words">{children}</td>
271
+ ),
272
+
273
+ // Horizontal rule
274
+ hr: () => (
275
+ <hr className="my-3 border-0 h-px bg-border" />
276
+ ),
277
+
278
+ // Strong and emphasis
279
+ strong: ({ children }) => (
280
+ <strong className="font-semibold">{children}</strong>
281
+ ),
282
+ em: ({ children }) => (
283
+ <em className="italic">{children}</em>
284
+ ),
285
+ });
286
+
287
+ /**
288
+ * MarkdownMessage - Renders markdown content with syntax highlighting and GFM support
289
+ *
290
+ * Features:
291
+ * - GitHub Flavored Markdown (GFM) support
292
+ * - Syntax highlighted code blocks with copy button
293
+ * - Mermaid diagram rendering
294
+ * - Tables, lists, blockquotes
295
+ * - User/assistant styling modes
296
+ *
297
+ * @example
298
+ * ```tsx
299
+ * <MarkdownMessage content="# Hello\n\nThis is **bold** text." />
300
+ *
301
+ * // User message styling
302
+ * <MarkdownMessage content="Some content" isUser />
303
+ * ```
304
+ */
305
+ export const MarkdownMessage: React.FC<MarkdownMessageProps> = ({
306
+ content,
307
+ className = "",
308
+ isUser = false,
309
+ }) => {
310
+ const components = React.useMemo(() => createMarkdownComponents(isUser), [isUser]);
311
+
312
+ return (
313
+ <div
314
+ className={`
315
+ prose prose-sm max-w-none break-words overflow-hidden text-sm
316
+ ${isUser ? 'prose-invert' : 'dark:prose-invert'}
317
+ ${className}
318
+ `}
319
+ style={{
320
+ // Inherit colors from parent - fixes issues with external CSS variables
321
+ '--tw-prose-body': 'inherit',
322
+ '--tw-prose-headings': 'inherit',
323
+ '--tw-prose-bold': 'inherit',
324
+ '--tw-prose-links': 'inherit',
325
+ color: 'inherit',
326
+ } as React.CSSProperties}
327
+ >
328
+ <ReactMarkdown
329
+ remarkPlugins={[remarkGfm]}
330
+ components={components}
331
+ >
332
+ {content}
333
+ </ReactMarkdown>
334
+ </div>
335
+ );
336
+ };
337
+
338
+ export default MarkdownMessage;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Markdown Components
3
+ */
4
+
5
+ export { MarkdownMessage, type MarkdownMessageProps } from './MarkdownMessage';