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.
- package/App.tsx +182 -0
- package/README.md +330 -0
- package/assets/xertica-logo.svg +38 -0
- package/assets/xertica-x-logo.svg +21 -0
- package/bin/cli.ts +193 -0
- package/components/AssistenteXertica.tsx +2003 -0
- package/components/AudioPlayer.tsx +203 -0
- package/components/CodeBlock.tsx +242 -0
- package/components/DocumentEditor.tsx +504 -0
- package/components/ForgotPasswordPage.tsx +170 -0
- package/components/FormattedDocument.tsx +87 -0
- package/components/HomeContent.tsx +123 -0
- package/components/HomePage.tsx +70 -0
- package/components/LanguageSelector.tsx +54 -0
- package/components/LoginPage.tsx +199 -0
- package/components/MarkdownMessage.tsx +62 -0
- package/components/ModernChatInput.tsx +502 -0
- package/components/PodcastPlayer.tsx +409 -0
- package/components/ResetPasswordPage.tsx +234 -0
- package/components/Sidebar.tsx +489 -0
- package/components/TemplateContent.tsx +629 -0
- package/components/TemplatePage.tsx +70 -0
- package/components/ThemeToggle.tsx +65 -0
- package/components/VerifyEmailPage.tsx +187 -0
- package/components/XerticaLogo.tsx +69 -0
- package/components/XerticaOrbe.tsx +1339 -0
- package/components/XerticaXLogo.tsx +53 -0
- package/components/examples/DrawingMapExample.tsx +530 -0
- package/components/examples/FilterableMapExample.tsx +380 -0
- package/components/examples/LocationPickerExample.tsx +330 -0
- package/components/examples/MapExamples.tsx +280 -0
- package/components/examples/MapShowcase.tsx +446 -0
- package/components/examples/RouteMapExamples.tsx +329 -0
- package/components/examples/SimpleFilterableMap.tsx +192 -0
- package/components/examples/index.ts +52 -0
- package/components/figma/ImageWithFallback.tsx +27 -0
- package/components/index.ts +44 -0
- package/components/media/AudioPlayer.tsx +278 -0
- package/components/media/FloatingMediaWrapper.tsx +166 -0
- package/components/media/VideoPlayer.tsx +285 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +159 -0
- package/components/ui/alert.tsx +91 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +65 -0
- package/components/ui/badge.tsx +55 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button.tsx +78 -0
- package/components/ui/calendar.tsx +235 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +353 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +177 -0
- package/components/ui/context-menu.tsx +252 -0
- package/components/ui/dialog.tsx +138 -0
- package/components/ui/drawer.tsx +134 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/empty.tsx +90 -0
- package/components/ui/file-upload.tsx +152 -0
- package/components/ui/form.tsx +195 -0
- package/components/ui/google-maps-loader.tsx +379 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/index.ts +242 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +38 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/map-config.ts +12 -0
- package/components/ui/map-layers.tsx +129 -0
- package/components/ui/map.exports.ts +31 -0
- package/components/ui/map.tsx +412 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/navigation-menu.tsx +162 -0
- package/components/ui/notification-badge.tsx +61 -0
- package/components/ui/page-header.tsx +229 -0
- package/components/ui/pagination.tsx +127 -0
- package/components/ui/popover.tsx +48 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +56 -0
- package/components/ui/rating.tsx +102 -0
- package/components/ui/resizable.tsx +405 -0
- package/components/ui/route-map.tsx +246 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/search.tsx +70 -0
- package/components/ui/select.tsx +176 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +726 -0
- package/components/ui/simple-map.tsx +92 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/slider.tsx +58 -0
- package/components/ui/sonner.tsx +77 -0
- package/components/ui/stats-card.tsx +84 -0
- package/components/ui/stepper.tsx +126 -0
- package/components/ui/switch.tsx +34 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +66 -0
- package/components/ui/textarea.tsx +26 -0
- package/components/ui/timeline.tsx +140 -0
- package/components/ui/toggle-group.tsx +71 -0
- package/components/ui/toggle.tsx +46 -0
- package/components/ui/tooltip.tsx +61 -0
- package/components/ui/tree-view.tsx +123 -0
- package/components/ui/use-mobile.ts +24 -0
- package/components/ui/utils.ts +6 -0
- package/components/ui/xertica-assistant.tsx +1420 -0
- package/contexts/ApiKeyContext.tsx +123 -0
- package/contexts/AssistenteContext.tsx +118 -0
- package/contexts/BrandColorsContext.tsx +551 -0
- package/contexts/LanguageContext.tsx +36 -0
- package/contexts/ThemeContext.tsx +85 -0
- package/dist/cli.js +20922 -0
- package/eslint.config.js +41 -0
- package/guidelines/Guidelines.md +61 -0
- package/hooks/useTheme.ts +4 -0
- package/imports/Podcast.tsx +389 -0
- package/imports/XerticaAi.tsx +46 -0
- package/imports/XerticaX.tsx +20 -0
- package/imports/svg-aueiaqngck.ts +11 -0
- package/imports/svg-v9krss1ozd.ts +16 -0
- package/imports/svg-vhrdofe3qe.ts +5 -0
- package/index.css +4448 -0
- package/index.html +14 -0
- package/main.tsx +10 -0
- package/package.json +119 -0
- package/postcss.config.js +6 -0
- package/routes.tsx +33 -0
- package/styles/globals.css +15 -0
- package/styles/xertica/app-overrides/chat.css +61 -0
- package/styles/xertica/app-overrides/scrollbar.css +33 -0
- package/styles/xertica/base.css +70 -0
- package/styles/xertica/integrations/google-maps.css +76 -0
- package/styles/xertica/integrations/sonner.css +73 -0
- package/styles/xertica/theme-map.css +88 -0
- package/styles/xertica/tokens.css +190 -0
- package/tsconfig.json +31 -0
- package/tsconfig.node.json +10 -0
- package/utils/gemini.ts +140 -0
- package/vite-env.d.ts +12 -0
- package/vite.config.ts +36 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
function NavigationMenu({
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
viewport = true,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
|
14
|
+
viewport?: boolean;
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<NavigationMenuPrimitive.Root
|
|
18
|
+
data-slot="navigation-menu"
|
|
19
|
+
className={cn(
|
|
20
|
+
"relative z-10 flex w-full flex-1 items-center justify-center",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
{viewport && <NavigationMenuViewport />}
|
|
27
|
+
</NavigationMenuPrimitive.Root>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function NavigationMenuList({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
|
35
|
+
return (
|
|
36
|
+
<NavigationMenuPrimitive.List
|
|
37
|
+
data-slot="navigation-menu-list"
|
|
38
|
+
className={cn(
|
|
39
|
+
"group flex flex-1 list-none items-center justify-center gap-1",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function NavigationMenuItem({
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
|
51
|
+
return (
|
|
52
|
+
<NavigationMenuPrimitive.Item
|
|
53
|
+
data-slot="navigation-menu-item"
|
|
54
|
+
className={cn("relative", className)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const navigationMenuTriggerStyle = cva(
|
|
61
|
+
"group inline-flex h-10 w-max items-center justify-center rounded-[var(--radius)] bg-background px-4 py-2 text-[var(--text-p)] font-[var(--font-weight-medium)] hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent/50 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
function NavigationMenuTrigger({
|
|
65
|
+
className,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
|
69
|
+
return (
|
|
70
|
+
<NavigationMenuPrimitive.Trigger
|
|
71
|
+
data-slot="navigation-menu-trigger"
|
|
72
|
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
{children}{" "}
|
|
76
|
+
<ChevronDownIcon
|
|
77
|
+
className="relative top-[1px] ml-1 size-3 group-data-[state=open]:rotate-180"
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
/>
|
|
80
|
+
</NavigationMenuPrimitive.Trigger>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function NavigationMenuContent({
|
|
85
|
+
className,
|
|
86
|
+
...props
|
|
87
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
|
88
|
+
return (
|
|
89
|
+
<NavigationMenuPrimitive.Content
|
|
90
|
+
data-slot="navigation-menu-content"
|
|
91
|
+
className={cn(
|
|
92
|
+
"left-0 top-0 w-full md:w-auto",
|
|
93
|
+
className
|
|
94
|
+
)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function NavigationMenuViewport({
|
|
101
|
+
className,
|
|
102
|
+
...props
|
|
103
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
|
104
|
+
return (
|
|
105
|
+
<div className={cn("absolute left-0 top-full flex justify-center w-full")}>
|
|
106
|
+
<NavigationMenuPrimitive.Viewport
|
|
107
|
+
data-slot="navigation-menu-viewport"
|
|
108
|
+
className={cn(
|
|
109
|
+
"origin-top-center relative mt-1.5 overflow-hidden rounded-[var(--radius)] border border-border bg-popover text-popover-foreground shadow-lg",
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function NavigationMenuLink({
|
|
119
|
+
className,
|
|
120
|
+
...props
|
|
121
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
|
122
|
+
return (
|
|
123
|
+
<NavigationMenuPrimitive.Link
|
|
124
|
+
data-slot="navigation-menu-link"
|
|
125
|
+
className={cn(
|
|
126
|
+
"block select-none space-y-1 rounded-[var(--radius)] p-3 leading-none no-underline outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
|
|
127
|
+
className
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function NavigationMenuIndicator({
|
|
135
|
+
className,
|
|
136
|
+
...props
|
|
137
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
|
138
|
+
return (
|
|
139
|
+
<NavigationMenuPrimitive.Indicator
|
|
140
|
+
data-slot="navigation-menu-indicator"
|
|
141
|
+
className={cn(
|
|
142
|
+
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
|
143
|
+
className
|
|
144
|
+
)}
|
|
145
|
+
{...props}
|
|
146
|
+
>
|
|
147
|
+
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
|
148
|
+
</NavigationMenuPrimitive.Indicator>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
NavigationMenu,
|
|
154
|
+
NavigationMenuList,
|
|
155
|
+
NavigationMenuItem,
|
|
156
|
+
NavigationMenuContent,
|
|
157
|
+
NavigationMenuTrigger,
|
|
158
|
+
NavigationMenuLink,
|
|
159
|
+
NavigationMenuIndicator,
|
|
160
|
+
NavigationMenuViewport,
|
|
161
|
+
navigationMenuTriggerStyle,
|
|
162
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "./utils";
|
|
3
|
+
|
|
4
|
+
interface NotificationBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
count?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
showZero?: boolean;
|
|
8
|
+
dot?: boolean;
|
|
9
|
+
variant?: "default" | "primary" | "destructive";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const NotificationBadge = React.forwardRef<HTMLDivElement, NotificationBadgeProps>(
|
|
13
|
+
({
|
|
14
|
+
className,
|
|
15
|
+
count = 0,
|
|
16
|
+
max = 99,
|
|
17
|
+
showZero = false,
|
|
18
|
+
dot = false,
|
|
19
|
+
variant = "destructive",
|
|
20
|
+
children,
|
|
21
|
+
...props
|
|
22
|
+
}, ref) => {
|
|
23
|
+
const displayCount = count > max ? `${max}+` : count;
|
|
24
|
+
const shouldShow = count > 0 || showZero;
|
|
25
|
+
|
|
26
|
+
const variantStyles = {
|
|
27
|
+
default: "bg-muted text-muted-foreground",
|
|
28
|
+
primary: "bg-primary text-primary-foreground",
|
|
29
|
+
destructive: "bg-destructive text-destructive-foreground",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!shouldShow && !dot) {
|
|
33
|
+
return <div ref={ref} className={className}>{children}</div>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div ref={ref} className={cn("relative inline-block", className)} {...props}>
|
|
38
|
+
{children}
|
|
39
|
+
<span
|
|
40
|
+
className={cn(
|
|
41
|
+
"absolute -right-1 -top-1 flex items-center justify-center rounded-full",
|
|
42
|
+
variantStyles[variant],
|
|
43
|
+
dot
|
|
44
|
+
? "h-2 w-2"
|
|
45
|
+
: "min-w-[1.25rem] h-5 px-1.5"
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
{!dot && shouldShow && (
|
|
49
|
+
<span className="text-[10px] font-medium leading-none">
|
|
50
|
+
{displayCount}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
NotificationBadge.displayName = "NotificationBadge";
|
|
59
|
+
|
|
60
|
+
export { NotificationBadge };
|
|
61
|
+
export type { NotificationBadgeProps };
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronRight, Moon, Sun, Globe } from "lucide-react";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from "./dropdown-menu";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Types & Interfaces
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
export interface BreadcrumbItem {
|
|
17
|
+
label: string;
|
|
18
|
+
href?: string;
|
|
19
|
+
icon?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Language {
|
|
23
|
+
code: string;
|
|
24
|
+
label: string;
|
|
25
|
+
flag?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PageHeaderProps {
|
|
29
|
+
/**
|
|
30
|
+
* Array de items do breadcrumb
|
|
31
|
+
*/
|
|
32
|
+
breadcrumbs: BreadcrumbItem[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Mostra o seletor de idioma
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
showLanguageSelector?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Idiomas disponíveis
|
|
42
|
+
*/
|
|
43
|
+
languages?: Language[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Idioma atual selecionado (código)
|
|
47
|
+
*/
|
|
48
|
+
currentLanguage?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Callback quando o idioma é alterado
|
|
52
|
+
*/
|
|
53
|
+
onLanguageChange?: (languageCode: string) => void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mostra o alternador de dark mode
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
showThemeToggle?: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Tema atual ('light' ou 'dark')
|
|
63
|
+
*/
|
|
64
|
+
theme?: "light" | "dark";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Callback quando o tema é alterado
|
|
68
|
+
*/
|
|
69
|
+
onThemeChange?: (theme: "light" | "dark") => void;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Classes CSS adicionais
|
|
73
|
+
*/
|
|
74
|
+
className?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Conteúdo adicional à direita
|
|
78
|
+
*/
|
|
79
|
+
rightContent?: React.ReactNode;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Component
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
export function PageHeader({
|
|
87
|
+
breadcrumbs,
|
|
88
|
+
showLanguageSelector = false,
|
|
89
|
+
languages = [
|
|
90
|
+
{ code: "pt-BR", label: "Português (BR)", flag: "🇧🇷" },
|
|
91
|
+
{ code: "en-US", label: "English (US)", flag: "🇺🇸" },
|
|
92
|
+
{ code: "es-ES", label: "Español", flag: "🇪🇸" },
|
|
93
|
+
],
|
|
94
|
+
currentLanguage = "pt-BR",
|
|
95
|
+
onLanguageChange,
|
|
96
|
+
showThemeToggle = false,
|
|
97
|
+
theme = "light",
|
|
98
|
+
onThemeChange,
|
|
99
|
+
className,
|
|
100
|
+
rightContent,
|
|
101
|
+
}: PageHeaderProps) {
|
|
102
|
+
const [isDark, setIsDark] = React.useState(theme === "dark");
|
|
103
|
+
const [selectedLanguage, setSelectedLanguage] = React.useState(currentLanguage);
|
|
104
|
+
|
|
105
|
+
// Sincroniza estado interno com props
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
setIsDark(theme === "dark");
|
|
108
|
+
}, [theme]);
|
|
109
|
+
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
setSelectedLanguage(currentLanguage);
|
|
112
|
+
}, [currentLanguage]);
|
|
113
|
+
|
|
114
|
+
const handleThemeToggle = () => {
|
|
115
|
+
const newTheme = isDark ? "light" : "dark";
|
|
116
|
+
setIsDark(!isDark);
|
|
117
|
+
onThemeChange?.(newTheme);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleLanguageChange = (languageCode: string) => {
|
|
121
|
+
setSelectedLanguage(languageCode);
|
|
122
|
+
onLanguageChange?.(languageCode);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const currentLang = languages.find((lang) => lang.code === selectedLanguage);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<header
|
|
129
|
+
className={cn(
|
|
130
|
+
"sticky top-0 z-40 w-full border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",
|
|
131
|
+
className
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<div className="container flex h-14 md:h-16 items-center justify-between px-4 md:px-6">
|
|
135
|
+
{/* Breadcrumb */}
|
|
136
|
+
<nav aria-label="Breadcrumb" className="flex items-center gap-1 md:gap-2 overflow-x-auto">
|
|
137
|
+
{breadcrumbs.map((item, index) => {
|
|
138
|
+
const isLast = index === breadcrumbs.length - 1;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<React.Fragment key={index}>
|
|
142
|
+
{index > 0 && (
|
|
143
|
+
<ChevronRight className="w-3 h-3 md:w-4 md:h-4 text-muted-foreground shrink-0" />
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{item.href && !isLast ? (
|
|
147
|
+
<a
|
|
148
|
+
href={item.href}
|
|
149
|
+
className="flex items-center gap-1.5 md:gap-2 text-sm md:text-base text-muted-foreground hover:text-foreground transition-colors whitespace-nowrap"
|
|
150
|
+
>
|
|
151
|
+
{item.icon && <span className="shrink-0">{item.icon}</span>}
|
|
152
|
+
<span>{item.label}</span>
|
|
153
|
+
</a>
|
|
154
|
+
) : (
|
|
155
|
+
<span
|
|
156
|
+
className={cn(
|
|
157
|
+
"flex items-center gap-1.5 md:gap-2 text-sm md:text-base whitespace-nowrap",
|
|
158
|
+
isLast ? "text-foreground font-medium" : "text-muted-foreground"
|
|
159
|
+
)}
|
|
160
|
+
aria-current={isLast ? "page" : undefined}
|
|
161
|
+
>
|
|
162
|
+
{item.icon && <span className="shrink-0">{item.icon}</span>}
|
|
163
|
+
<span>{item.label}</span>
|
|
164
|
+
</span>
|
|
165
|
+
)}
|
|
166
|
+
</React.Fragment>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</nav>
|
|
170
|
+
|
|
171
|
+
{/* Right Side Actions */}
|
|
172
|
+
<div className="flex items-center gap-2 md:gap-3 shrink-0">
|
|
173
|
+
{/* Language Selector */}
|
|
174
|
+
{showLanguageSelector && (
|
|
175
|
+
<DropdownMenu>
|
|
176
|
+
<DropdownMenuTrigger asChild>
|
|
177
|
+
<Button
|
|
178
|
+
variant="ghost"
|
|
179
|
+
size="sm"
|
|
180
|
+
className="gap-1.5 md:gap-2 h-8 md:h-9 px-2 md:px-3"
|
|
181
|
+
>
|
|
182
|
+
<Globe className="w-4 h-4" />
|
|
183
|
+
<span className="hidden sm:inline-flex items-center gap-1.5">
|
|
184
|
+
{currentLang?.flag && <span>{currentLang.flag}</span>}
|
|
185
|
+
<span>{currentLang?.label.split(" ")[0]}</span>
|
|
186
|
+
</span>
|
|
187
|
+
</Button>
|
|
188
|
+
</DropdownMenuTrigger>
|
|
189
|
+
<DropdownMenuContent align="end">
|
|
190
|
+
{languages.map((lang) => (
|
|
191
|
+
<DropdownMenuItem
|
|
192
|
+
key={lang.code}
|
|
193
|
+
onClick={() => handleLanguageChange(lang.code)}
|
|
194
|
+
className={cn(
|
|
195
|
+
"gap-2 cursor-pointer",
|
|
196
|
+
lang.code === selectedLanguage && "bg-accent"
|
|
197
|
+
)}
|
|
198
|
+
>
|
|
199
|
+
{lang.flag && <span>{lang.flag}</span>}
|
|
200
|
+
<span>{lang.label}</span>
|
|
201
|
+
</DropdownMenuItem>
|
|
202
|
+
))}
|
|
203
|
+
</DropdownMenuContent>
|
|
204
|
+
</DropdownMenu>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{/* Theme Toggle */}
|
|
208
|
+
{showThemeToggle && (
|
|
209
|
+
<Button
|
|
210
|
+
variant="ghost"
|
|
211
|
+
size="sm"
|
|
212
|
+
onClick={handleThemeToggle}
|
|
213
|
+
className="h-8 md:h-9 w-8 md:w-9 p-0"
|
|
214
|
+
aria-label={isDark ? "Mudar para modo claro" : "Mudar para modo escuro"}
|
|
215
|
+
>
|
|
216
|
+
{isDark ? (
|
|
217
|
+
<Sun className="w-4 h-4" />
|
|
218
|
+
) : (
|
|
219
|
+
<Moon className="w-4 h-4" />
|
|
220
|
+
)}
|
|
221
|
+
</Button>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{rightContent}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</header>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ChevronLeftIcon,
|
|
4
|
+
ChevronRightIcon,
|
|
5
|
+
MoreHorizontalIcon,
|
|
6
|
+
} from "lucide-react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "./utils";
|
|
9
|
+
import { Button, buttonVariants } from "./button";
|
|
10
|
+
|
|
11
|
+
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
|
12
|
+
return (
|
|
13
|
+
<nav
|
|
14
|
+
role="navigation"
|
|
15
|
+
aria-label="pagination"
|
|
16
|
+
data-slot="pagination"
|
|
17
|
+
className={cn("mx-auto flex w-full justify-center", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function PaginationContent({
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<"ul">) {
|
|
27
|
+
return (
|
|
28
|
+
<ul
|
|
29
|
+
data-slot="pagination-content"
|
|
30
|
+
className={cn("flex flex-row items-center gap-1", className)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
|
37
|
+
return <li data-slot="pagination-item" {...props} />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type PaginationLinkProps = {
|
|
41
|
+
isActive?: boolean;
|
|
42
|
+
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
|
43
|
+
React.ComponentProps<"a">;
|
|
44
|
+
|
|
45
|
+
function PaginationLink({
|
|
46
|
+
className,
|
|
47
|
+
isActive,
|
|
48
|
+
size = "icon",
|
|
49
|
+
...props
|
|
50
|
+
}: PaginationLinkProps) {
|
|
51
|
+
return (
|
|
52
|
+
<a
|
|
53
|
+
aria-current={isActive ? "page" : undefined}
|
|
54
|
+
data-slot="pagination-link"
|
|
55
|
+
data-active={isActive}
|
|
56
|
+
className={cn(
|
|
57
|
+
buttonVariants({
|
|
58
|
+
variant: isActive ? "outline" : "ghost",
|
|
59
|
+
size,
|
|
60
|
+
}),
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function PaginationPrevious({
|
|
69
|
+
className,
|
|
70
|
+
...props
|
|
71
|
+
}: React.ComponentProps<typeof PaginationLink>) {
|
|
72
|
+
return (
|
|
73
|
+
<PaginationLink
|
|
74
|
+
aria-label="Go to previous page"
|
|
75
|
+
size="default"
|
|
76
|
+
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<ChevronLeftIcon />
|
|
80
|
+
<span className="hidden sm:block">Previous</span>
|
|
81
|
+
</PaginationLink>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function PaginationNext({
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
}: React.ComponentProps<typeof PaginationLink>) {
|
|
89
|
+
return (
|
|
90
|
+
<PaginationLink
|
|
91
|
+
aria-label="Go to next page"
|
|
92
|
+
size="default"
|
|
93
|
+
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
|
94
|
+
{...props}
|
|
95
|
+
>
|
|
96
|
+
<span className="hidden sm:block">Next</span>
|
|
97
|
+
<ChevronRightIcon />
|
|
98
|
+
</PaginationLink>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function PaginationEllipsis({
|
|
103
|
+
className,
|
|
104
|
+
...props
|
|
105
|
+
}: React.ComponentProps<"span">) {
|
|
106
|
+
return (
|
|
107
|
+
<span
|
|
108
|
+
aria-hidden
|
|
109
|
+
data-slot="pagination-ellipsis"
|
|
110
|
+
className={cn("flex size-9 items-center justify-center", className)}
|
|
111
|
+
{...props}
|
|
112
|
+
>
|
|
113
|
+
<MoreHorizontalIcon className="size-4" />
|
|
114
|
+
<span className="sr-only">More pages</span>
|
|
115
|
+
</span>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
Pagination,
|
|
121
|
+
PaginationContent,
|
|
122
|
+
PaginationLink,
|
|
123
|
+
PaginationItem,
|
|
124
|
+
PaginationPrevious,
|
|
125
|
+
PaginationNext,
|
|
126
|
+
PaginationEllipsis,
|
|
127
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
function Popover({
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
11
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function PopoverTrigger({
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
17
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function PopoverContent({
|
|
21
|
+
className,
|
|
22
|
+
align = "center",
|
|
23
|
+
sideOffset = 4,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
26
|
+
return (
|
|
27
|
+
<PopoverPrimitive.Portal>
|
|
28
|
+
<PopoverPrimitive.Content
|
|
29
|
+
data-slot="popover-content"
|
|
30
|
+
align={align}
|
|
31
|
+
sideOffset={sideOffset}
|
|
32
|
+
className={cn(
|
|
33
|
+
"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 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</PopoverPrimitive.Portal>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function PopoverAnchor({
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
45
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
function Progress({
|
|
9
|
+
className,
|
|
10
|
+
value,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
|
13
|
+
return (
|
|
14
|
+
<ProgressPrimitive.Root
|
|
15
|
+
data-slot="progress"
|
|
16
|
+
className={cn(
|
|
17
|
+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
<ProgressPrimitive.Indicator
|
|
23
|
+
data-slot="progress-indicator"
|
|
24
|
+
className="bg-primary h-full w-full flex-1 transition-all"
|
|
25
|
+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
26
|
+
/>
|
|
27
|
+
</ProgressPrimitive.Root>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Progress };
|