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,412 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { cn } from './utils';
4
+ import { useGoogleMapsLoader } from './google-maps-loader';
5
+ import { useMapLayers, MapLayersConfig } from './map-layers';
6
+
7
+ export interface MapProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ center?: { lat: number; lng: number };
9
+ zoom?: number;
10
+ markers?: Array<{
11
+ position: { lat: number; lng: number };
12
+ label?: string;
13
+ title?: string;
14
+ info?: string;
15
+ customColor?: string;
16
+ icon?: string;
17
+ iconSvg?: string;
18
+ iconColor?: string;
19
+ infoWindowContent?: string; // Custom HTML content for InfoWindow
20
+ richContent?: React.ReactNode; // Custom React component for InfoWindow
21
+ }>[];
22
+ circle?: {
23
+ center: { lat: number; lng: number };
24
+ radius: number;
25
+ fillColor?: string;
26
+ strokeColor?: string;
27
+ };
28
+ polygon?: {
29
+ paths: Array<{ lat: number; lng: number }>[];
30
+ fillColor?: string;
31
+ strokeColor?: string;
32
+ };
33
+ layers?: MapLayersConfig;
34
+ height?: string;
35
+ apiKey?: string;
36
+ mapContainerClassName?: string;
37
+ disableDefaultUI?: boolean;
38
+ zoomControl?: boolean;
39
+ streetViewControl?: boolean;
40
+ mapTypeControl?: boolean;
41
+ fullscreenControl?: boolean;
42
+ gestureHandling?: 'cooperative' | 'greedy' | 'none' | 'auto';
43
+ onMapLoad?: (map: google.maps.Map) => void;
44
+ }
45
+
46
+ const DEFAULT_CENTER = { lat: -23.5505, lng: -46.6333 };
47
+ const DEFAULT_ZOOM = 12;
48
+
49
+ const MapContent = React.forwardRef<HTMLDivElement, MapProps & { apiKey: string }>(
50
+ ({ apiKey, ...props }, ref) => {
51
+ const { isLoaded, loadError } = useGoogleMapsLoader();
52
+ const {
53
+ center = DEFAULT_CENTER,
54
+ zoom = DEFAULT_ZOOM,
55
+ markers = [],
56
+ circle,
57
+ polygon,
58
+ layers,
59
+ height = "400px",
60
+ mapContainerClassName,
61
+ disableDefaultUI = false,
62
+ zoomControl = true,
63
+ streetViewControl = false,
64
+ mapTypeControl = false,
65
+ fullscreenControl = true,
66
+ gestureHandling = 'cooperative',
67
+ onMapLoad,
68
+ className,
69
+ ...divProps
70
+ } = props;
71
+
72
+ const [selectedMarker, setSelectedMarker] = useState<number | null>(null);
73
+ const mapRef = useRef<google.maps.Map | null>(null);
74
+ const mapContainerRef = useRef<HTMLDivElement>(null);
75
+ const markersRef = useRef<google.maps.marker.AdvancedMarkerElement[]>([]);
76
+ const infoWindowRef = useRef<google.maps.InfoWindow | null>(null);
77
+ const circleRef = useRef<google.maps.Circle | null>(null);
78
+ const polygonRef = useRef<google.maps.Polygon | null>(null);
79
+ const isInitializedRef = useRef(false);
80
+
81
+ // Resolve theme colors for Map shapes (Circle, Polygon)
82
+ const [themeColors, setThemeColors] = useState({
83
+ primary: '#4F46E5',
84
+ chart2: '#10B981'
85
+ });
86
+
87
+ useEffect(() => {
88
+ if (typeof window !== 'undefined') {
89
+ const styles = getComputedStyle(document.documentElement);
90
+ setThemeColors({
91
+ primary: styles.getPropertyValue('--primary').trim() || '#4F46E5',
92
+ chart2: styles.getPropertyValue('--chart-2').trim() || '#10B981'
93
+ });
94
+ }
95
+ }, []);
96
+
97
+ // Initialize map once
98
+ useEffect(() => {
99
+ if (!isLoaded || !mapContainerRef.current || isInitializedRef.current) return;
100
+
101
+ isInitializedRef.current = true;
102
+ const map = new google.maps.Map(mapContainerRef.current, {
103
+ center,
104
+ zoom,
105
+ mapId: 'xertica-map',
106
+ disableDefaultUI,
107
+ zoomControl,
108
+ streetViewControl,
109
+ mapTypeControl,
110
+ fullscreenControl,
111
+ gestureHandling,
112
+ });
113
+
114
+ mapRef.current = map;
115
+
116
+ if (onMapLoad) {
117
+ onMapLoad(map);
118
+ }
119
+
120
+ return () => {
121
+ isInitializedRef.current = false;
122
+ mapRef.current = null;
123
+ };
124
+ }, [isLoaded]);
125
+
126
+ // Update markers
127
+ useEffect(() => {
128
+ const map = mapRef.current;
129
+ if (!map || !isLoaded || !window.google?.maps?.marker) return;
130
+
131
+ // Clear old markers
132
+ markersRef.current.forEach(m => { m.map = null; });
133
+ markersRef.current = [];
134
+
135
+ // Create new markers
136
+ markers.forEach((markerData, idx) => {
137
+ const markerColor = markerData.customColor || 'var(--primary)';
138
+ const iconColor = markerData.iconColor || 'white';
139
+
140
+ const markerContent = document.createElement('div');
141
+ markerContent.className = 'flex items-center justify-center w-8 h-8 border-[3px] border-background shadow-lg cursor-pointer origin-center transition-transform duration-200';
142
+ // Custom shape using standard border-radius classes if possible, or style for specific shape
143
+ markerContent.style.borderRadius = '50% 50% 50% 0';
144
+ markerContent.style.transform = 'rotate(-45deg)';
145
+ markerContent.style.backgroundColor = markerColor;
146
+ // box-shadow is handled by shadow-lg, border by border-background
147
+
148
+ const iconContainer = document.createElement('div');
149
+ iconContainer.className = 'flex items-center justify-center rotate-45';
150
+
151
+ if (markerData.iconSvg) {
152
+ const div = document.createElement('div');
153
+ div.innerHTML = markerData.iconSvg;
154
+ const svg = div.querySelector('svg');
155
+ if (svg) {
156
+ svg.setAttribute('width', '16');
157
+ svg.setAttribute('height', '16');
158
+ svg.style.color = iconColor;
159
+ svg.style.fill = 'currentColor';
160
+ iconContainer.appendChild(svg);
161
+ }
162
+ } else if (markerData.icon || markerData.label) {
163
+ const span = document.createElement('span');
164
+ span.textContent = markerData.icon || markerData.label || '';
165
+ span.style.color = iconColor;
166
+ span.className = 'font-semibold text-sm';
167
+ iconContainer.appendChild(span);
168
+ }
169
+
170
+ markerContent.appendChild(iconContainer);
171
+
172
+ markerContent.addEventListener('mouseenter', () => {
173
+ markerContent.style.transform = 'rotate(-45deg) scale(1.15)';
174
+ });
175
+ markerContent.addEventListener('mouseleave', () => {
176
+ markerContent.style.transform = 'rotate(-45deg) scale(1)';
177
+ });
178
+
179
+ const marker = new google.maps.marker.AdvancedMarkerElement({
180
+ map,
181
+ position: markerData.position,
182
+ content: markerContent,
183
+ title: markerData.title,
184
+ });
185
+
186
+ marker.addListener('click', () => {
187
+ setSelectedMarker(idx);
188
+ map.panTo(markerData.position);
189
+ });
190
+
191
+ markersRef.current.push(marker);
192
+ });
193
+
194
+ return () => {
195
+ markersRef.current.forEach(m => { m.map = null; });
196
+ markersRef.current = [];
197
+ };
198
+ }, [isLoaded, markers]);
199
+
200
+ // Update InfoWindow
201
+ useEffect(() => {
202
+ const map = mapRef.current;
203
+ if (!map || selectedMarker === null) {
204
+ infoWindowRef.current?.close();
205
+ return;
206
+ }
207
+
208
+ const markerData = markers[selectedMarker];
209
+ if (!markerData) return;
210
+
211
+ // Determine content to render
212
+ let contentToRender = markerData.richContent;
213
+
214
+ // If no richContent but title/info exists, create default standard content
215
+ if (!contentToRender && (markerData.title || markerData.info)) {
216
+ contentToRender = (
217
+ <div className="p-4 pr-8 min-w-[200px] max-w-[300px]">
218
+ {markerData.title && <h4 className="font-semibold text-base mb-1">{markerData.title}</h4>}
219
+ {markerData.info && <p className="text-sm text-muted-foreground">{markerData.info}</p>}
220
+ </div>
221
+ );
222
+ }
223
+
224
+ // Render content using React Root if we have something to render
225
+ if (contentToRender) {
226
+ if (!infoWindowRef.current) {
227
+ infoWindowRef.current = new google.maps.InfoWindow();
228
+ }
229
+
230
+ const container = document.createElement('div');
231
+ const root = createRoot(container);
232
+ root.render(contentToRender);
233
+
234
+ infoWindowRef.current.setContent(container);
235
+ infoWindowRef.current.open({
236
+ map,
237
+ anchor: markersRef.current[selectedMarker],
238
+ });
239
+
240
+ const listener = infoWindowRef.current.addListener('closeclick', () => {
241
+ setSelectedMarker(null);
242
+ // Small delay to ensure close animation finishes
243
+ setTimeout(() => root.unmount(), 0);
244
+ });
245
+
246
+ return () => {
247
+ google.maps.event.removeListener(listener);
248
+ // Safe unmount
249
+ setTimeout(() => root.unmount(), 0);
250
+ };
251
+ }
252
+ }, [selectedMarker, markers]);
253
+
254
+ // Update circle
255
+ useEffect(() => {
256
+ const map = mapRef.current;
257
+ if (!map || !isLoaded) return;
258
+
259
+ circleRef.current?.setMap(null);
260
+ circleRef.current = null;
261
+
262
+ if (circle && circle.center && circle.radius) {
263
+ circleRef.current = new google.maps.Circle({
264
+ map,
265
+ center: circle.center,
266
+ radius: circle.radius,
267
+ fillColor: circle.fillColor || themeColors.primary,
268
+ fillOpacity: 0.2,
269
+ strokeColor: circle.strokeColor || themeColors.primary,
270
+ strokeOpacity: 0.8,
271
+ strokeWeight: 2,
272
+ });
273
+ }
274
+
275
+ return () => {
276
+ circleRef.current?.setMap(null);
277
+ };
278
+ }, [circle, isLoaded]);
279
+
280
+ // Update polygon
281
+ useEffect(() => {
282
+ const map = mapRef.current;
283
+ if (!map || !isLoaded) return;
284
+
285
+ polygonRef.current?.setMap(null);
286
+ polygonRef.current = null;
287
+
288
+ if (polygon && polygon.paths) {
289
+ polygonRef.current = new google.maps.Polygon({
290
+ map,
291
+ paths: polygon.paths,
292
+ fillColor: polygon.fillColor || themeColors.chart2,
293
+ fillOpacity: 0.2,
294
+ strokeColor: polygon.strokeColor || themeColors.chart2,
295
+ strokeOpacity: 0.8,
296
+ strokeWeight: 2,
297
+ });
298
+ }
299
+
300
+ return () => {
301
+ polygonRef.current?.setMap(null);
302
+ };
303
+ }, [polygon, isLoaded]);
304
+
305
+ // Layers
306
+ useMapLayers(mapRef.current, layers || {});
307
+
308
+ if (loadError) {
309
+ return (
310
+ <div
311
+ ref={ref}
312
+ className={cn(
313
+ "relative rounded-[var(--radius-card)] border border-destructive/50 overflow-hidden bg-destructive/5",
314
+ className
315
+ )}
316
+ style={{ height }}
317
+ {...divProps}
318
+ >
319
+ <div className="absolute inset-0 flex items-center justify-center">
320
+ <div className="text-center space-y-2 p-6">
321
+ <div className="text-destructive">
322
+ <svg className="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
323
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
324
+ </svg>
325
+ </div>
326
+ <p className="text-sm text-destructive">Failed to load Google Maps</p>
327
+ <p className="text-xs text-muted-foreground">Check API key in Settings</p>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ );
332
+ }
333
+
334
+ if (!isLoaded) {
335
+ return (
336
+ <div
337
+ ref={ref}
338
+ className={cn(
339
+ "relative rounded-[var(--radius-card)] border border-border overflow-hidden bg-muted animate-pulse",
340
+ className
341
+ )}
342
+ style={{ height }}
343
+ {...divProps}
344
+ />
345
+ );
346
+ }
347
+
348
+ return (
349
+ <div
350
+ ref={ref}
351
+ className={cn(
352
+ "relative rounded-[var(--radius-card)] border border-border overflow-hidden",
353
+ className
354
+ )}
355
+ {...divProps}
356
+ >
357
+ <div
358
+ ref={mapContainerRef}
359
+ style={{ height }}
360
+ className={cn("rounded-[var(--radius-card)] w-full", mapContainerClassName)}
361
+ />
362
+ </div>
363
+ );
364
+ }
365
+ );
366
+ MapContent.displayName = "MapContent";
367
+
368
+ export const Map = React.forwardRef<HTMLDivElement, MapProps>(
369
+ (props, ref) => {
370
+ const effectiveApiKey = props.apiKey || "";
371
+
372
+ const isValidKey = effectiveApiKey &&
373
+ effectiveApiKey !== "YOUR_GOOGLE_MAPS_API_KEY_HERE" &&
374
+ effectiveApiKey.startsWith("AIza");
375
+
376
+ if (!isValidKey) {
377
+ const {
378
+ center, zoom, markers, circle, polygon, height, apiKey,
379
+ mapContainerClassName, disableDefaultUI, zoomControl,
380
+ streetViewControl, mapTypeControl, fullscreenControl,
381
+ gestureHandling, layers, ...divProps
382
+ } = props;
383
+
384
+ return (
385
+ <div
386
+ ref={ref}
387
+ className={cn(
388
+ "relative rounded-[var(--radius-card)] border border-border overflow-hidden bg-muted",
389
+ props.className
390
+ )}
391
+ style={{ height: props.height || "400px" }}
392
+ {...divProps}
393
+ >
394
+ <div className="absolute inset-0 flex items-center justify-center bg-muted">
395
+ <div className="text-center space-y-3 p-6">
396
+ <div className="w-16 h-16 mx-auto bg-primary/10 rounded-full flex items-center justify-center">
397
+ <svg className="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
398
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
399
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
400
+ </svg>
401
+ </div>
402
+ <p className="text-sm text-muted-foreground">Configure Google Maps API Key in Settings</p>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ );
407
+ }
408
+
409
+ return <MapContent ref={ref} {...props} apiKey={effectiveApiKey} />;
410
+ }
411
+ );
412
+ Map.displayName = "Map";
@@ -0,0 +1,276 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as MenubarPrimitive from "@radix-ui/react-menubar";
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
6
+
7
+ import { cn } from "./utils";
8
+
9
+ function Menubar({
10
+ className,
11
+ ...props
12
+ }: React.ComponentProps<typeof MenubarPrimitive.Root>) {
13
+ return (
14
+ <MenubarPrimitive.Root
15
+ data-slot="menubar"
16
+ className={cn(
17
+ "bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ function MenubarMenu({
26
+ ...props
27
+ }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
28
+ return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />;
29
+ }
30
+
31
+ function MenubarGroup({
32
+ ...props
33
+ }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
34
+ return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />;
35
+ }
36
+
37
+ function MenubarPortal({
38
+ ...props
39
+ }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
40
+ return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />;
41
+ }
42
+
43
+ function MenubarRadioGroup({
44
+ ...props
45
+ }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
46
+ return (
47
+ <MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
48
+ );
49
+ }
50
+
51
+ function MenubarTrigger({
52
+ className,
53
+ ...props
54
+ }: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
55
+ return (
56
+ <MenubarPrimitive.Trigger
57
+ data-slot="menubar-trigger"
58
+ className={cn(
59
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
60
+ className,
61
+ )}
62
+ {...props}
63
+ />
64
+ );
65
+ }
66
+
67
+ function MenubarContent({
68
+ className,
69
+ align = "start",
70
+ alignOffset = -4,
71
+ sideOffset = 8,
72
+ ...props
73
+ }: React.ComponentProps<typeof MenubarPrimitive.Content>) {
74
+ return (
75
+ <MenubarPortal>
76
+ <MenubarPrimitive.Content
77
+ data-slot="menubar-content"
78
+ align={align}
79
+ alignOffset={alignOffset}
80
+ sideOffset={sideOffset}
81
+ className={cn(
82
+ "bg-popover text-popover-foreground data-[state=open]:animate-in 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 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
83
+ className,
84
+ )}
85
+ {...props}
86
+ />
87
+ </MenubarPortal>
88
+ );
89
+ }
90
+
91
+ function MenubarItem({
92
+ className,
93
+ inset,
94
+ variant = "default",
95
+ ...props
96
+ }: React.ComponentProps<typeof MenubarPrimitive.Item> & {
97
+ inset?: boolean;
98
+ variant?: "default" | "destructive";
99
+ }) {
100
+ return (
101
+ <MenubarPrimitive.Item
102
+ data-slot="menubar-item"
103
+ data-inset={inset}
104
+ data-variant={variant}
105
+ className={cn(
106
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
107
+ className,
108
+ )}
109
+ {...props}
110
+ />
111
+ );
112
+ }
113
+
114
+ function MenubarCheckboxItem({
115
+ className,
116
+ children,
117
+ checked,
118
+ ...props
119
+ }: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
120
+ return (
121
+ <MenubarPrimitive.CheckboxItem
122
+ data-slot="menubar-checkbox-item"
123
+ className={cn(
124
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
125
+ className,
126
+ )}
127
+ checked={checked}
128
+ {...props}
129
+ >
130
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
131
+ <MenubarPrimitive.ItemIndicator>
132
+ <CheckIcon className="size-4" />
133
+ </MenubarPrimitive.ItemIndicator>
134
+ </span>
135
+ {children}
136
+ </MenubarPrimitive.CheckboxItem>
137
+ );
138
+ }
139
+
140
+ function MenubarRadioItem({
141
+ className,
142
+ children,
143
+ ...props
144
+ }: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
145
+ return (
146
+ <MenubarPrimitive.RadioItem
147
+ data-slot="menubar-radio-item"
148
+ className={cn(
149
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
150
+ className,
151
+ )}
152
+ {...props}
153
+ >
154
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
155
+ <MenubarPrimitive.ItemIndicator>
156
+ <CircleIcon className="size-2 fill-current" />
157
+ </MenubarPrimitive.ItemIndicator>
158
+ </span>
159
+ {children}
160
+ </MenubarPrimitive.RadioItem>
161
+ );
162
+ }
163
+
164
+ function MenubarLabel({
165
+ className,
166
+ inset,
167
+ ...props
168
+ }: React.ComponentProps<typeof MenubarPrimitive.Label> & {
169
+ inset?: boolean;
170
+ }) {
171
+ return (
172
+ <MenubarPrimitive.Label
173
+ data-slot="menubar-label"
174
+ data-inset={inset}
175
+ className={cn(
176
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
177
+ className,
178
+ )}
179
+ {...props}
180
+ />
181
+ );
182
+ }
183
+
184
+ function MenubarSeparator({
185
+ className,
186
+ ...props
187
+ }: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
188
+ return (
189
+ <MenubarPrimitive.Separator
190
+ data-slot="menubar-separator"
191
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
192
+ {...props}
193
+ />
194
+ );
195
+ }
196
+
197
+ function MenubarShortcut({
198
+ className,
199
+ ...props
200
+ }: React.ComponentProps<"span">) {
201
+ return (
202
+ <span
203
+ data-slot="menubar-shortcut"
204
+ className={cn(
205
+ "text-muted-foreground ml-auto text-xs tracking-widest",
206
+ className,
207
+ )}
208
+ {...props}
209
+ />
210
+ );
211
+ }
212
+
213
+ function MenubarSub({
214
+ ...props
215
+ }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
216
+ return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
217
+ }
218
+
219
+ function MenubarSubTrigger({
220
+ className,
221
+ inset,
222
+ children,
223
+ ...props
224
+ }: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
225
+ inset?: boolean;
226
+ }) {
227
+ return (
228
+ <MenubarPrimitive.SubTrigger
229
+ data-slot="menubar-sub-trigger"
230
+ data-inset={inset}
231
+ className={cn(
232
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
233
+ className,
234
+ )}
235
+ {...props}
236
+ >
237
+ {children}
238
+ <ChevronRightIcon className="ml-auto h-4 w-4" />
239
+ </MenubarPrimitive.SubTrigger>
240
+ );
241
+ }
242
+
243
+ function MenubarSubContent({
244
+ className,
245
+ ...props
246
+ }: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
247
+ return (
248
+ <MenubarPrimitive.SubContent
249
+ data-slot="menubar-sub-content"
250
+ className={cn(
251
+ "bg-popover text-popover-foreground 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 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
252
+ className,
253
+ )}
254
+ {...props}
255
+ />
256
+ );
257
+ }
258
+
259
+ export {
260
+ Menubar,
261
+ MenubarPortal,
262
+ MenubarMenu,
263
+ MenubarTrigger,
264
+ MenubarContent,
265
+ MenubarGroup,
266
+ MenubarSeparator,
267
+ MenubarLabel,
268
+ MenubarItem,
269
+ MenubarShortcut,
270
+ MenubarCheckboxItem,
271
+ MenubarRadioGroup,
272
+ MenubarRadioItem,
273
+ MenubarSub,
274
+ MenubarSubTrigger,
275
+ MenubarSubContent,
276
+ };