xertica-ui 1.0.0

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 (141) hide show
  1. package/App.tsx +182 -0
  2. package/README.md +330 -0
  3. package/assets/xertica-logo.svg +38 -0
  4. package/assets/xertica-x-logo.svg +21 -0
  5. package/bin/cli.ts +193 -0
  6. package/components/AssistenteXertica.tsx +2003 -0
  7. package/components/AudioPlayer.tsx +203 -0
  8. package/components/CodeBlock.tsx +242 -0
  9. package/components/DocumentEditor.tsx +504 -0
  10. package/components/ForgotPasswordPage.tsx +170 -0
  11. package/components/FormattedDocument.tsx +87 -0
  12. package/components/HomeContent.tsx +123 -0
  13. package/components/HomePage.tsx +70 -0
  14. package/components/LanguageSelector.tsx +54 -0
  15. package/components/LoginPage.tsx +199 -0
  16. package/components/MarkdownMessage.tsx +62 -0
  17. package/components/ModernChatInput.tsx +502 -0
  18. package/components/PodcastPlayer.tsx +409 -0
  19. package/components/ResetPasswordPage.tsx +234 -0
  20. package/components/Sidebar.tsx +489 -0
  21. package/components/TemplateContent.tsx +629 -0
  22. package/components/TemplatePage.tsx +70 -0
  23. package/components/ThemeToggle.tsx +65 -0
  24. package/components/VerifyEmailPage.tsx +187 -0
  25. package/components/XerticaLogo.tsx +69 -0
  26. package/components/XerticaOrbe.tsx +1339 -0
  27. package/components/XerticaXLogo.tsx +53 -0
  28. package/components/examples/DrawingMapExample.tsx +530 -0
  29. package/components/examples/FilterableMapExample.tsx +380 -0
  30. package/components/examples/LocationPickerExample.tsx +330 -0
  31. package/components/examples/MapExamples.tsx +280 -0
  32. package/components/examples/MapShowcase.tsx +446 -0
  33. package/components/examples/RouteMapExamples.tsx +329 -0
  34. package/components/examples/SimpleFilterableMap.tsx +192 -0
  35. package/components/examples/index.ts +52 -0
  36. package/components/figma/ImageWithFallback.tsx +27 -0
  37. package/components/index.ts +44 -0
  38. package/components/media/AudioPlayer.tsx +278 -0
  39. package/components/media/FloatingMediaWrapper.tsx +166 -0
  40. package/components/media/VideoPlayer.tsx +285 -0
  41. package/components/ui/accordion.tsx +66 -0
  42. package/components/ui/alert-dialog.tsx +159 -0
  43. package/components/ui/alert.tsx +91 -0
  44. package/components/ui/aspect-ratio.tsx +11 -0
  45. package/components/ui/avatar.tsx +65 -0
  46. package/components/ui/badge.tsx +55 -0
  47. package/components/ui/breadcrumb.tsx +109 -0
  48. package/components/ui/button.tsx +78 -0
  49. package/components/ui/calendar.tsx +235 -0
  50. package/components/ui/card.tsx +92 -0
  51. package/components/ui/carousel.tsx +241 -0
  52. package/components/ui/chart.tsx +353 -0
  53. package/components/ui/checkbox.tsx +32 -0
  54. package/components/ui/collapsible.tsx +33 -0
  55. package/components/ui/command.tsx +177 -0
  56. package/components/ui/context-menu.tsx +252 -0
  57. package/components/ui/dialog.tsx +138 -0
  58. package/components/ui/drawer.tsx +134 -0
  59. package/components/ui/dropdown-menu.tsx +257 -0
  60. package/components/ui/empty.tsx +90 -0
  61. package/components/ui/file-upload.tsx +152 -0
  62. package/components/ui/form.tsx +195 -0
  63. package/components/ui/google-maps-loader.tsx +379 -0
  64. package/components/ui/hover-card.tsx +44 -0
  65. package/components/ui/index.ts +242 -0
  66. package/components/ui/input-otp.tsx +77 -0
  67. package/components/ui/input.tsx +38 -0
  68. package/components/ui/label.tsx +24 -0
  69. package/components/ui/map-config.ts +12 -0
  70. package/components/ui/map-layers.tsx +129 -0
  71. package/components/ui/map.exports.ts +31 -0
  72. package/components/ui/map.tsx +412 -0
  73. package/components/ui/menubar.tsx +276 -0
  74. package/components/ui/navigation-menu.tsx +162 -0
  75. package/components/ui/notification-badge.tsx +61 -0
  76. package/components/ui/page-header.tsx +229 -0
  77. package/components/ui/pagination.tsx +127 -0
  78. package/components/ui/popover.tsx +48 -0
  79. package/components/ui/progress.tsx +31 -0
  80. package/components/ui/radio-group.tsx +56 -0
  81. package/components/ui/rating.tsx +102 -0
  82. package/components/ui/resizable.tsx +405 -0
  83. package/components/ui/route-map.tsx +246 -0
  84. package/components/ui/scroll-area.tsx +58 -0
  85. package/components/ui/search.tsx +70 -0
  86. package/components/ui/select.tsx +176 -0
  87. package/components/ui/separator.tsx +28 -0
  88. package/components/ui/sheet.tsx +138 -0
  89. package/components/ui/sidebar.tsx +726 -0
  90. package/components/ui/simple-map.tsx +92 -0
  91. package/components/ui/skeleton.tsx +13 -0
  92. package/components/ui/slider.tsx +58 -0
  93. package/components/ui/sonner.tsx +77 -0
  94. package/components/ui/stats-card.tsx +84 -0
  95. package/components/ui/stepper.tsx +126 -0
  96. package/components/ui/switch.tsx +34 -0
  97. package/components/ui/table.tsx +116 -0
  98. package/components/ui/tabs.tsx +66 -0
  99. package/components/ui/textarea.tsx +26 -0
  100. package/components/ui/timeline.tsx +140 -0
  101. package/components/ui/toggle-group.tsx +71 -0
  102. package/components/ui/toggle.tsx +46 -0
  103. package/components/ui/tooltip.tsx +61 -0
  104. package/components/ui/tree-view.tsx +123 -0
  105. package/components/ui/use-mobile.ts +24 -0
  106. package/components/ui/utils.ts +6 -0
  107. package/components/ui/xertica-assistant.tsx +1420 -0
  108. package/contexts/ApiKeyContext.tsx +123 -0
  109. package/contexts/AssistenteContext.tsx +118 -0
  110. package/contexts/BrandColorsContext.tsx +551 -0
  111. package/contexts/LanguageContext.tsx +36 -0
  112. package/contexts/ThemeContext.tsx +85 -0
  113. package/dist/cli.js +20922 -0
  114. package/eslint.config.js +41 -0
  115. package/guidelines/Guidelines.md +61 -0
  116. package/hooks/useTheme.ts +4 -0
  117. package/imports/Podcast.tsx +389 -0
  118. package/imports/XerticaAi.tsx +46 -0
  119. package/imports/XerticaX.tsx +20 -0
  120. package/imports/svg-aueiaqngck.ts +11 -0
  121. package/imports/svg-v9krss1ozd.ts +16 -0
  122. package/imports/svg-vhrdofe3qe.ts +5 -0
  123. package/index.css +4448 -0
  124. package/index.html +14 -0
  125. package/main.tsx +10 -0
  126. package/package.json +119 -0
  127. package/postcss.config.js +6 -0
  128. package/routes.tsx +33 -0
  129. package/styles/globals.css +15 -0
  130. package/styles/xertica/app-overrides/chat.css +61 -0
  131. package/styles/xertica/app-overrides/scrollbar.css +33 -0
  132. package/styles/xertica/base.css +70 -0
  133. package/styles/xertica/integrations/google-maps.css +76 -0
  134. package/styles/xertica/integrations/sonner.css +73 -0
  135. package/styles/xertica/theme-map.css +88 -0
  136. package/styles/xertica/tokens.css +190 -0
  137. package/tsconfig.json +31 -0
  138. package/tsconfig.node.json +10 -0
  139. package/utils/gemini.ts +140 -0
  140. package/vite-env.d.ts +12 -0
  141. package/vite.config.ts +36 -0
@@ -0,0 +1,56 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5
+
6
+ import { cn } from "./utils"
7
+
8
+ const RadioGroup = React.forwardRef<
9
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <RadioGroupPrimitive.Root
13
+ className={cn("grid gap-4", className)}
14
+ {...props}
15
+ ref={ref}
16
+ />
17
+ ))
18
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
19
+
20
+ const RadioGroupItem = React.forwardRef<
21
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
22
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
23
+ >(({ className, ...props }, ref) => (
24
+ <RadioGroupPrimitive.Item
25
+ ref={ref}
26
+ data-slot="radio-group-item"
27
+ className={cn(
28
+ // Base styles - Material UI inspired
29
+ "relative aspect-square size-5 shrink-0 rounded-full border-2 transition-all duration-200 outline-none cursor-pointer",
30
+ // Default state - sempre com contorno usando variáveis CSS
31
+ "border-muted-foreground bg-background",
32
+ // Hover state - usa variável primary
33
+ "hover:border-primary hover:shadow-md",
34
+ // Focus state - usa variável primary
35
+ "focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
36
+ // Checked state - mantém contorno com primary
37
+ "data-[state=checked]:border-primary data-[state=checked]:bg-background",
38
+ // Disabled state
39
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:border-muted",
40
+ // Invalid state - usa variável destructive
41
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
42
+ className
43
+ )}
44
+ {...props}
45
+ >
46
+ <RadioGroupPrimitive.Indicator
47
+ data-slot="radio-group-indicator"
48
+ className="flex items-center justify-center"
49
+ >
50
+ <div className="size-2.5 rounded-full bg-primary transition-all duration-200" />
51
+ </RadioGroupPrimitive.Indicator>
52
+ </RadioGroupPrimitive.Item>
53
+ ))
54
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
55
+
56
+ export { RadioGroup, RadioGroupItem }
@@ -0,0 +1,102 @@
1
+ import * as React from "react";
2
+ import { Star } from "lucide-react";
3
+ import { cn } from "./utils";
4
+
5
+ interface RatingProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
6
+ value?: number;
7
+ onChange?: (value: number) => void;
8
+ max?: number;
9
+ readonly?: boolean;
10
+ size?: "sm" | "md" | "lg";
11
+ showValue?: boolean;
12
+ }
13
+
14
+ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
15
+ ({
16
+ className,
17
+ value = 0,
18
+ onChange,
19
+ max = 5,
20
+ readonly = false,
21
+ size = "md",
22
+ showValue = false,
23
+ ...props
24
+ }, ref) => {
25
+ const [hoverValue, setHoverValue] = React.useState<number | null>(null);
26
+
27
+ const sizeStyles = {
28
+ sm: "h-4 w-4",
29
+ md: "h-5 w-5",
30
+ lg: "h-6 w-6",
31
+ };
32
+
33
+ const handleClick = (rating: number) => {
34
+ if (!readonly && onChange) {
35
+ onChange(rating);
36
+ }
37
+ };
38
+
39
+ const handleMouseEnter = (rating: number) => {
40
+ if (!readonly) {
41
+ setHoverValue(rating);
42
+ }
43
+ };
44
+
45
+ const handleMouseLeave = () => {
46
+ if (!readonly) {
47
+ setHoverValue(null);
48
+ }
49
+ };
50
+
51
+ const displayValue = hoverValue ?? value;
52
+
53
+ return (
54
+ <div
55
+ ref={ref}
56
+ className={cn("flex items-center gap-1", className)}
57
+ {...props}
58
+ >
59
+ <div className="flex items-center gap-0.5">
60
+ {Array.from({ length: max }, (_, index) => {
61
+ const rating = index + 1;
62
+ const isFilled = rating <= displayValue;
63
+
64
+ return (
65
+ <button
66
+ key={index}
67
+ type="button"
68
+ onClick={() => handleClick(rating)}
69
+ onMouseEnter={() => handleMouseEnter(rating)}
70
+ onMouseLeave={handleMouseLeave}
71
+ disabled={readonly}
72
+ className={cn(
73
+ "transition-colors focus:outline-none",
74
+ !readonly && "cursor-pointer hover:scale-110",
75
+ readonly && "cursor-default"
76
+ )}
77
+ >
78
+ <Star
79
+ className={cn(
80
+ sizeStyles[size],
81
+ isFilled
82
+ ? "fill-[rgb(245,158,11)] text-[rgb(245,158,11)]"
83
+ : "fill-none text-muted-foreground"
84
+ )}
85
+ />
86
+ </button>
87
+ );
88
+ })}
89
+ </div>
90
+ {showValue && (
91
+ <span className="ml-2 text-muted-foreground">
92
+ {value.toFixed(1)}
93
+ </span>
94
+ )}
95
+ </div>
96
+ );
97
+ }
98
+ );
99
+ Rating.displayName = "Rating";
100
+
101
+ export { Rating };
102
+ export type { RatingProps };
@@ -0,0 +1,405 @@
1
+ "use client";
2
+
3
+ import { GripVerticalIcon } from "lucide-react";
4
+ import * as React from "react";
5
+ import { cn } from "./utils";
6
+
7
+ // --- Types ---
8
+
9
+ type PanelData = {
10
+ id: string;
11
+ ref: React.MutableRefObject<HTMLDivElement | null>;
12
+ defaultSize?: number;
13
+ minSize?: number;
14
+ maxSize?: number;
15
+ collapsible?: boolean;
16
+ onCollapse?: () => void;
17
+ onExpand?: () => void;
18
+ onResize?: (size: number) => void;
19
+ };
20
+
21
+ type HandleData = {
22
+ id: string;
23
+ ref: React.MutableRefObject<HTMLDivElement | null>;
24
+ };
25
+
26
+ type ResizableContextType = {
27
+ direction: "horizontal" | "vertical";
28
+ registerPanel: (data: PanelData) => void;
29
+ unregisterPanel: (id: string) => void;
30
+ registerHandle: (data: HandleData) => void;
31
+ unregisterHandle: (id: string) => void;
32
+ isDragging: boolean;
33
+ startDragging: (handleId: string, event: React.MouseEvent | React.TouchEvent) => void;
34
+ getPanelStyle: (id: string) => React.CSSProperties;
35
+ };
36
+
37
+ const ResizableContext = React.createContext<ResizableContextType | null>(null);
38
+
39
+ // --- Components ---
40
+
41
+ const ResizablePanelGroup = ({
42
+ children,
43
+ className,
44
+ direction = "horizontal",
45
+ id,
46
+ autoSaveId,
47
+ storage,
48
+ onLayout,
49
+ ...props
50
+ }: {
51
+ children: React.ReactNode;
52
+ className?: string;
53
+ direction?: "horizontal" | "vertical";
54
+ id?: string;
55
+ autoSaveId?: string;
56
+ storage?: any;
57
+ onLayout?: (sizes: number[]) => void;
58
+ } & React.HTMLAttributes<HTMLDivElement>) => {
59
+ const [panels, setPanels] = React.useState<Map<string, PanelData>>(new Map());
60
+ const [handles, setHandles] = React.useState<Map<string, HandleData>>(new Map());
61
+ const [sizes, setSizes] = React.useState<Map<string, number>>(new Map());
62
+ const [isDragging, setIsDragging] = React.useState(false);
63
+ const containerRef = React.useRef<HTMLDivElement>(null);
64
+
65
+ // Sorting helpers
66
+ const getSortedItems = React.useCallback(<T extends { ref: React.MutableRefObject<HTMLDivElement | null> }>(
67
+ items: Map<string, T>
68
+ ) => {
69
+ if (!containerRef.current) return [];
70
+ return Array.from(items.values()).sort((a, b) => {
71
+ if (!a.ref.current || !b.ref.current) return 0;
72
+ const position = a.ref.current.compareDocumentPosition(b.ref.current);
73
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
74
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
75
+ return 0;
76
+ });
77
+ }, []);
78
+
79
+ // Initialize layout
80
+ React.useEffect(() => {
81
+ const sortedPanels = getSortedItems(panels);
82
+ if (sortedPanels.length === 0) return;
83
+
84
+ // Check if we need to initialize sizes
85
+ const uninitialized = sortedPanels.some(p => !sizes.has(p.id));
86
+ if (uninitialized) {
87
+ const newSizes = new Map(sizes);
88
+ const remainingSpace = 100;
89
+ const defaultSizeCount = sortedPanels.filter(p => p.defaultSize).length;
90
+
91
+ let usedSpace = 0;
92
+ sortedPanels.forEach(p => {
93
+ if (p.defaultSize) usedSpace += p.defaultSize;
94
+ });
95
+
96
+ const spaceForOthers = Math.max(0, remainingSpace - usedSpace);
97
+ const countOthers = sortedPanels.length - defaultSizeCount;
98
+ const sizeForOthers = countOthers > 0 ? spaceForOthers / countOthers : 0;
99
+
100
+ sortedPanels.forEach(p => {
101
+ if (!newSizes.has(p.id)) {
102
+ newSizes.set(p.id, p.defaultSize ?? sizeForOthers);
103
+ }
104
+ });
105
+ setSizes(newSizes);
106
+ }
107
+ }, [panels, sizes, getSortedItems]);
108
+
109
+ const registerPanel = React.useCallback((data: PanelData) => {
110
+ setPanels(prev => {
111
+ const next = new Map(prev);
112
+ next.set(data.id, data);
113
+ return next;
114
+ });
115
+ }, []);
116
+
117
+ const unregisterPanel = React.useCallback((id: string) => {
118
+ setPanels(prev => {
119
+ const next = new Map(prev);
120
+ next.delete(id);
121
+ return next;
122
+ });
123
+ setSizes(prev => {
124
+ const next = new Map(prev);
125
+ next.delete(id);
126
+ return next;
127
+ });
128
+ }, []);
129
+
130
+ const registerHandle = React.useCallback((data: HandleData) => {
131
+ setHandles(prev => {
132
+ const next = new Map(prev);
133
+ next.set(data.id, data);
134
+ return next;
135
+ });
136
+ }, []);
137
+
138
+ const unregisterHandle = React.useCallback((id: string) => {
139
+ setHandles(prev => {
140
+ const next = new Map(prev);
141
+ next.delete(id);
142
+ return next;
143
+ });
144
+ }, []);
145
+
146
+ const startDragging = React.useCallback((handleId: string, event: React.MouseEvent | React.TouchEvent) => {
147
+ event.preventDefault();
148
+ setIsDragging(true);
149
+
150
+ const sortedPanels = getSortedItems(panels);
151
+ const sortedHandles = getSortedItems(handles);
152
+
153
+ const handleIndex = sortedHandles.findIndex(h => h.id === handleId);
154
+ if (handleIndex === -1) return;
155
+
156
+ // Handle i usually sits between Panel i and Panel i+1
157
+ const leftPanel = sortedPanels[handleIndex];
158
+ const rightPanel = sortedPanels[handleIndex + 1];
159
+
160
+ if (!leftPanel || !rightPanel) return;
161
+
162
+ const startX = 'touches' in event ? event.touches[0].clientX : event.clientX;
163
+ const startY = 'touches' in event ? event.touches[0].clientY : event.clientY;
164
+
165
+ const startSizeLeft = sizes.get(leftPanel.id) || 0;
166
+ const startSizeRight = sizes.get(rightPanel.id) || 0;
167
+
168
+ const containerSize = direction === 'horizontal'
169
+ ? containerRef.current?.offsetWidth || 1
170
+ : containerRef.current?.offsetHeight || 1;
171
+
172
+ const onMove = (e: MouseEvent | TouchEvent) => {
173
+ const currentX = 'touches' in e ? e.touches[0].clientX : e.clientX;
174
+ const currentY = 'touches' in e ? e.touches[0].clientY : e.clientY;
175
+
176
+ const deltaPixels = direction === 'horizontal' ? currentX - startX : currentY - startY;
177
+ const deltaPercent = (deltaPixels / containerSize) * 100;
178
+
179
+ // Calculate tentative new sizes
180
+ let finalLeft = startSizeLeft + deltaPercent;
181
+ let finalRight = startSizeRight - deltaPercent;
182
+
183
+ // Apply min constraints
184
+ if (leftPanel.minSize !== undefined && finalLeft < leftPanel.minSize) {
185
+ const diff = leftPanel.minSize - finalLeft;
186
+ finalLeft = leftPanel.minSize;
187
+ finalRight -= diff;
188
+ }
189
+ if (rightPanel.minSize !== undefined && finalRight < rightPanel.minSize) {
190
+ const diff = rightPanel.minSize - finalRight;
191
+ finalRight = rightPanel.minSize;
192
+ finalLeft -= diff;
193
+ }
194
+
195
+ // Apply max constraints
196
+ if (leftPanel.maxSize !== undefined && finalLeft > leftPanel.maxSize) {
197
+ const diff = finalLeft - leftPanel.maxSize;
198
+ finalLeft = leftPanel.maxSize;
199
+ finalRight += diff;
200
+ }
201
+ if (rightPanel.maxSize !== undefined && finalRight > rightPanel.maxSize) {
202
+ const diff = finalRight - rightPanel.maxSize;
203
+ finalRight = rightPanel.maxSize;
204
+ finalLeft -= diff;
205
+ }
206
+
207
+ // Hard clamp to 0-100 just in case
208
+ finalLeft = Math.max(0, Math.min(100, finalLeft));
209
+ finalRight = Math.max(0, Math.min(100, finalRight));
210
+
211
+ setSizes(prev => {
212
+ const next = new Map(prev);
213
+ next.set(leftPanel.id, finalLeft);
214
+ next.set(rightPanel.id, finalRight);
215
+ return next;
216
+ });
217
+
218
+ // Notify panels
219
+ leftPanel.onResize?.(finalLeft);
220
+ rightPanel.onResize?.(finalRight);
221
+ };
222
+
223
+ const onUp = () => {
224
+ setIsDragging(false);
225
+ window.removeEventListener('mousemove', onMove);
226
+ window.removeEventListener('mouseup', onUp);
227
+ window.removeEventListener('touchmove', onMove);
228
+ window.removeEventListener('touchend', onUp);
229
+
230
+ // Trigger onLayout callback
231
+ if (onLayout) {
232
+ onLayout(sortedPanels.map(p => sizes.get(p.id) || 0));
233
+ }
234
+ };
235
+
236
+ window.addEventListener('mousemove', onMove);
237
+ window.addEventListener('mouseup', onUp);
238
+ window.addEventListener('touchmove', onMove);
239
+ window.addEventListener('touchend', onUp);
240
+ }, [panels, handles, sizes, direction, getSortedItems, onLayout]);
241
+
242
+ const getPanelStyle = React.useCallback((id: string) => {
243
+ const size = sizes.get(id);
244
+ if (size === undefined) return { flex: '1 1 0%', overflow: 'hidden' };
245
+ return { flex: `${size} 1 0%`, overflow: 'hidden' };
246
+ }, [sizes]);
247
+
248
+ const contextValue = React.useMemo(() => ({
249
+ direction,
250
+ registerPanel,
251
+ unregisterPanel,
252
+ registerHandle,
253
+ unregisterHandle,
254
+ isDragging,
255
+ startDragging,
256
+ getPanelStyle
257
+ }), [direction, registerPanel, unregisterPanel, registerHandle, unregisterHandle, isDragging, startDragging, getPanelStyle]);
258
+
259
+ return (
260
+ <ResizableContext.Provider value={contextValue}>
261
+ <div
262
+ ref={containerRef}
263
+ data-slot="resizable-panel-group"
264
+ data-panel-group-direction={direction}
265
+ className={cn(
266
+ "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
267
+ className
268
+ )}
269
+ {...props}
270
+ >
271
+ {children}
272
+ </div>
273
+ </ResizableContext.Provider>
274
+ );
275
+ };
276
+
277
+ const ResizablePanel = ({
278
+ className,
279
+ defaultSize,
280
+ minSize = 0,
281
+ maxSize = 100,
282
+ collapsible,
283
+ collapsedSize,
284
+ onCollapse,
285
+ onExpand,
286
+ onResize,
287
+ order,
288
+ tagName,
289
+ id: propId,
290
+ children,
291
+ ...props
292
+ }: {
293
+ defaultSize?: number;
294
+ minSize?: number;
295
+ maxSize?: number;
296
+ collapsible?: boolean;
297
+ collapsedSize?: number;
298
+ onCollapse?: () => void;
299
+ onExpand?: () => void;
300
+ onResize?: (size: number) => void;
301
+ order?: number;
302
+ tagName?: string;
303
+ } & React.HTMLAttributes<HTMLDivElement>) => {
304
+ const context = React.useContext(ResizableContext);
305
+ const ref = React.useRef<HTMLDivElement>(null);
306
+ const [generatedId] = React.useState(() => Math.random().toString(36).substr(2, 9));
307
+ const id = propId || generatedId;
308
+
309
+ // We destructure only stable functions for the dependency array to avoid infinite loops
310
+ const registerPanel = context?.registerPanel;
311
+ const unregisterPanel = context?.unregisterPanel;
312
+
313
+ React.useLayoutEffect(() => {
314
+ if (!registerPanel || !unregisterPanel) return;
315
+ registerPanel({
316
+ id,
317
+ ref,
318
+ defaultSize,
319
+ minSize,
320
+ maxSize,
321
+ collapsible,
322
+ onCollapse,
323
+ onExpand,
324
+ onResize
325
+ });
326
+ return () => unregisterPanel(id);
327
+ }, [registerPanel, unregisterPanel, id, defaultSize, minSize, maxSize, collapsible, onCollapse, onExpand, onResize]);
328
+
329
+ if (!context) {
330
+ return <div className={cn("flex-1", className)} {...props}>{children}</div>;
331
+ }
332
+
333
+ return (
334
+ <div
335
+ ref={ref}
336
+ data-slot="resizable-panel"
337
+ className={cn("relative transition-[flex-grow] duration-0", className)}
338
+ style={context.getPanelStyle(id)}
339
+ {...props}
340
+ >
341
+ {children}
342
+ </div>
343
+ );
344
+ };
345
+
346
+ const ResizableHandle = ({
347
+ withHandle,
348
+ className,
349
+ id: propId,
350
+ tagName,
351
+ ...props
352
+ }: {
353
+ withHandle?: boolean;
354
+ tagName?: string;
355
+ } & React.HTMLAttributes<HTMLDivElement>) => {
356
+ const context = React.useContext(ResizableContext);
357
+ const ref = React.useRef<HTMLDivElement>(null);
358
+ const [generatedId] = React.useState(() => Math.random().toString(36).substr(2, 9));
359
+ const id = propId || generatedId;
360
+
361
+ // We destructure only stable functions for the dependency array
362
+ const registerHandle = context?.registerHandle;
363
+ const unregisterHandle = context?.unregisterHandle;
364
+
365
+ React.useLayoutEffect(() => {
366
+ if (!registerHandle || !unregisterHandle) return;
367
+ registerHandle({ id, ref });
368
+ return () => unregisterHandle(id);
369
+ }, [registerHandle, unregisterHandle, id]);
370
+
371
+ const handleMouseDown = (e: React.MouseEvent | React.TouchEvent) => {
372
+ if (context) {
373
+ context.startDragging(id, e);
374
+ }
375
+ };
376
+
377
+ if (!context) return null;
378
+
379
+ return (
380
+ <div
381
+ ref={ref}
382
+ data-slot="resizable-handle"
383
+ className={cn(
384
+ "bg-border relative flex items-center justify-center focus-visible:outline-hidden",
385
+ "touch-none select-none",
386
+ context.direction === "vertical"
387
+ ? "h-px w-full cursor-row-resize after:left-0 after:h-1 after:w-full after:-translate-y-1/2 hover:after:h-4"
388
+ : "w-px h-full cursor-col-resize after:top-0 after:w-1 after:h-full after:-translate-x-1/2 hover:after:w-4",
389
+ "after:absolute after:z-10",
390
+ className
391
+ )}
392
+ onMouseDown={handleMouseDown}
393
+ onTouchStart={handleMouseDown}
394
+ {...props}
395
+ >
396
+ {withHandle && (
397
+ <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
398
+ <GripVerticalIcon className="size-2.5" />
399
+ </div>
400
+ )}
401
+ </div>
402
+ );
403
+ };
404
+
405
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };