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,92 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Map, MapProps } from './map';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SimpleMap - Wrapper simplificado para casos de uso comuns
|
|
6
|
+
*
|
|
7
|
+
* Use este componente quando precisar de mapas básicos sem configuração avançada.
|
|
8
|
+
* Para casos mais complexos, use o componente Map diretamente.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface SimpleMapProps extends Omit<MapProps, 'markers' | 'circle' | 'polygon'> {
|
|
12
|
+
/** Endereço ou nome do local */
|
|
13
|
+
address?: string;
|
|
14
|
+
/** Título do marcador */
|
|
15
|
+
markerTitle?: string;
|
|
16
|
+
/** Informação adicional no InfoWindow */
|
|
17
|
+
markerInfo?: string;
|
|
18
|
+
/** Mostrar um marcador no centro */
|
|
19
|
+
showMarker?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SimpleMap = React.forwardRef<HTMLDivElement, SimpleMapProps>(
|
|
23
|
+
(
|
|
24
|
+
{
|
|
25
|
+
center,
|
|
26
|
+
address,
|
|
27
|
+
markerTitle,
|
|
28
|
+
markerInfo,
|
|
29
|
+
showMarker = true,
|
|
30
|
+
zoom = 15,
|
|
31
|
+
height = "350px",
|
|
32
|
+
...props
|
|
33
|
+
},
|
|
34
|
+
ref
|
|
35
|
+
) => {
|
|
36
|
+
// Criar marcador automaticamente se showMarker estiver true
|
|
37
|
+
const markers = showMarker && center
|
|
38
|
+
? [
|
|
39
|
+
{
|
|
40
|
+
position: center,
|
|
41
|
+
title: markerTitle || address || "Local",
|
|
42
|
+
info: markerInfo || address,
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
: [];
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Map
|
|
49
|
+
ref={ref}
|
|
50
|
+
center={center}
|
|
51
|
+
zoom={zoom}
|
|
52
|
+
height={height}
|
|
53
|
+
markers={markers}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
SimpleMap.displayName = "SimpleMap";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* EXEMPLOS DE USO:
|
|
64
|
+
*
|
|
65
|
+
* 1. Mapa Simples com Marcador:
|
|
66
|
+
* ```tsx
|
|
67
|
+
* <SimpleMap
|
|
68
|
+
* center={{ lat: -23.5505, lng: -46.6333 }}
|
|
69
|
+
* markerTitle="Escritório Central"
|
|
70
|
+
* markerInfo="Avenida Paulista, 1000"
|
|
71
|
+
* />
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* 2. Mapa Sem Marcador (apenas visualização):
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <SimpleMap
|
|
77
|
+
* center={{ lat: -23.5505, lng: -46.6333 }}
|
|
78
|
+
* showMarker={false}
|
|
79
|
+
* zoom={10}
|
|
80
|
+
* />
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* 3. Mapa de Localização de Loja:
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <SimpleMap
|
|
86
|
+
* center={{ lat: -23.5505, lng: -46.6333 }}
|
|
87
|
+
* address="Rua Augusta, 2000 - São Paulo"
|
|
88
|
+
* zoom={16}
|
|
89
|
+
* height="400px"
|
|
90
|
+
* />
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "./utils";
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("bg-accent animate-pulse rounded-md", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SliderPrimitive from "@radix-ui/react-slider"
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils"
|
|
7
|
+
|
|
8
|
+
const Slider = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof SliderPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
|
11
|
+
>(({ className, ...props }, ref) => {
|
|
12
|
+
const { defaultValue, value, min = 0, max = 100 } = props
|
|
13
|
+
const _values = React.useMemo(
|
|
14
|
+
() =>
|
|
15
|
+
Array.isArray(value)
|
|
16
|
+
? value
|
|
17
|
+
: Array.isArray(defaultValue)
|
|
18
|
+
? defaultValue
|
|
19
|
+
: [min, max],
|
|
20
|
+
[value, defaultValue, min, max]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<SliderPrimitive.Root
|
|
25
|
+
ref={ref}
|
|
26
|
+
data-slot="slider"
|
|
27
|
+
className={cn(
|
|
28
|
+
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
<SliderPrimitive.Track
|
|
34
|
+
data-slot="slider-track"
|
|
35
|
+
className={cn(
|
|
36
|
+
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-4 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:mx-2 data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5 data-[orientation=vertical]:my-2"
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<SliderPrimitive.Range
|
|
40
|
+
data-slot="slider-range"
|
|
41
|
+
className={cn(
|
|
42
|
+
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
|
43
|
+
)}
|
|
44
|
+
/>
|
|
45
|
+
</SliderPrimitive.Track>
|
|
46
|
+
{Array.from({ length: _values.length }, (_, index) => (
|
|
47
|
+
<SliderPrimitive.Thumb
|
|
48
|
+
data-slot="slider-thumb"
|
|
49
|
+
key={index}
|
|
50
|
+
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
|
51
|
+
/>
|
|
52
|
+
))}
|
|
53
|
+
</SliderPrimitive.Root>
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
Slider.displayName = SliderPrimitive.Root.displayName
|
|
57
|
+
|
|
58
|
+
export { Slider }
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Toaster as Sonner, ToasterProps } from "sonner";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Toaster component for displaying toast notifications.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // In your root layout/app:
|
|
11
|
+
* import { Toaster } from 'xertica-ui';
|
|
12
|
+
*
|
|
13
|
+
* function App() {
|
|
14
|
+
* return (
|
|
15
|
+
* <>
|
|
16
|
+
* <YourApp />
|
|
17
|
+
* <Toaster />
|
|
18
|
+
* </>
|
|
19
|
+
* );
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // To show a toast:
|
|
23
|
+
* import { toast } from 'sonner';
|
|
24
|
+
* toast.success('Success message');
|
|
25
|
+
*/
|
|
26
|
+
const Toaster = ({ theme, ...props }: ToasterProps) => {
|
|
27
|
+
// If no theme is provided, detect from document
|
|
28
|
+
const [resolvedTheme, setResolvedTheme] = React.useState<ToasterProps["theme"]>(theme);
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (theme) {
|
|
32
|
+
setResolvedTheme(theme);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Auto-detect theme from document
|
|
37
|
+
const detectTheme = () => {
|
|
38
|
+
const isDark = document.documentElement.classList.contains('dark') ||
|
|
39
|
+
document.documentElement.getAttribute('data-theme') === 'dark';
|
|
40
|
+
setResolvedTheme(isDark ? 'dark' : 'light');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
detectTheme();
|
|
44
|
+
|
|
45
|
+
// Watch for theme changes
|
|
46
|
+
const observer = new MutationObserver(detectTheme);
|
|
47
|
+
observer.observe(document.documentElement, {
|
|
48
|
+
attributes: true,
|
|
49
|
+
attributeFilter: ['class', 'data-theme']
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return () => observer.disconnect();
|
|
53
|
+
}, [theme]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Sonner
|
|
57
|
+
theme={resolvedTheme}
|
|
58
|
+
className="toaster group"
|
|
59
|
+
expand={true}
|
|
60
|
+
toastOptions={{
|
|
61
|
+
classNames: {
|
|
62
|
+
toast: "group toast group-[.toaster]:bg-card group-[.toaster]:text-card-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
|
63
|
+
description: "group-[.toast]:text-muted-foreground",
|
|
64
|
+
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
|
65
|
+
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
|
66
|
+
success: "group-[.toaster]:bg-[color:var(--chart-2)]/10 group-[.toaster]:border-l-4 group-[.toaster]:border-l-[color:var(--chart-2)] group-[.toaster]:text-foreground [&>svg]:text-[color:var(--chart-2)]",
|
|
67
|
+
error: "group-[.toaster]:bg-destructive/10 group-[.toaster]:border-l-4 group-[.toaster]:border-l-destructive group-[.toaster]:text-foreground [&>svg]:text-destructive",
|
|
68
|
+
warning: "group-[.toaster]:bg-[color:var(--chart-3)]/10 group-[.toaster]:border-l-4 group-[.toaster]:border-l-[color:var(--chart-3)] group-[.toaster]:text-foreground [&>svg]:text-[color:var(--chart-3)]",
|
|
69
|
+
info: "group-[.toaster]:bg-[color:var(--chart-4)]/10 group-[.toaster]:border-l-4 group-[.toaster]:border-l-[color:var(--chart-4)] group-[.toaster]:text-foreground [&>svg]:text-[color:var(--chart-4)]",
|
|
70
|
+
},
|
|
71
|
+
}}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export { Toaster };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
import { Card, CardContent } from "./card";
|
|
5
|
+
|
|
6
|
+
interface StatsCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
title: string;
|
|
8
|
+
value: string | number;
|
|
9
|
+
description?: string;
|
|
10
|
+
trend?: {
|
|
11
|
+
value: number;
|
|
12
|
+
label?: string;
|
|
13
|
+
};
|
|
14
|
+
icon?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const StatsCard = React.forwardRef<HTMLDivElement, StatsCardProps>(
|
|
18
|
+
({ className, title, value, description, trend, icon, ...props }, ref) => {
|
|
19
|
+
const getTrendIcon = () => {
|
|
20
|
+
if (!trend) return null;
|
|
21
|
+
|
|
22
|
+
if (trend.value > 0) {
|
|
23
|
+
return <TrendingUp className="h-4 w-4 text-[rgb(5,150,105)]" />;
|
|
24
|
+
} else if (trend.value < 0) {
|
|
25
|
+
return <TrendingDown className="h-4 w-4 text-destructive" />;
|
|
26
|
+
} else {
|
|
27
|
+
return <Minus className="h-4 w-4 text-muted-foreground" />;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getTrendColor = () => {
|
|
32
|
+
if (!trend) return "";
|
|
33
|
+
|
|
34
|
+
if (trend.value > 0) {
|
|
35
|
+
return "text-[rgb(5,150,105)]";
|
|
36
|
+
} else if (trend.value < 0) {
|
|
37
|
+
return "text-destructive";
|
|
38
|
+
} else {
|
|
39
|
+
return "text-muted-foreground";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Card ref={ref} className={cn("", className)} {...props}>
|
|
45
|
+
<CardContent className="p-4 sm:p-6">
|
|
46
|
+
<div className="flex items-start justify-between gap-3">
|
|
47
|
+
<div className="flex-1 min-w-0">
|
|
48
|
+
<p className="text-muted-foreground mb-1 text-sm">
|
|
49
|
+
{title}
|
|
50
|
+
</p>
|
|
51
|
+
<div className="flex flex-wrap items-baseline gap-2">
|
|
52
|
+
<h3 className="text-foreground text-xl sm:text-2xl">
|
|
53
|
+
{value}
|
|
54
|
+
</h3>
|
|
55
|
+
{trend && (
|
|
56
|
+
<div className={cn("flex items-center gap-1 text-sm", getTrendColor())}>
|
|
57
|
+
{getTrendIcon()}
|
|
58
|
+
<span>
|
|
59
|
+
{Math.abs(trend.value)}%
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
{(description || trend?.label) && (
|
|
65
|
+
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">
|
|
66
|
+
{trend?.label || description}
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
{icon && (
|
|
71
|
+
<div className="rounded-[var(--radius)] bg-muted p-2 sm:p-3 text-muted-foreground flex-shrink-0">
|
|
72
|
+
{icon}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
StatsCard.displayName = "StatsCard";
|
|
82
|
+
|
|
83
|
+
export { StatsCard };
|
|
84
|
+
export type { StatsCardProps };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Check } from "lucide-react";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
|
|
5
|
+
interface StepperContextValue {
|
|
6
|
+
currentStep: number;
|
|
7
|
+
totalSteps: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const StepperContext = React.createContext<StepperContextValue | undefined>(
|
|
11
|
+
undefined
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const useStepper = () => {
|
|
15
|
+
const context = React.useContext(StepperContext);
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("useStepper must be used within a Stepper");
|
|
18
|
+
}
|
|
19
|
+
return context;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
currentStep: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
|
|
27
|
+
({ currentStep, className, children, ...props }, ref) => {
|
|
28
|
+
const totalSteps = React.Children.count(children);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<StepperContext.Provider value={{ currentStep, totalSteps }}>
|
|
32
|
+
<div
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn("w-full", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<div className="flex items-center justify-between">
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</StepperContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
Stepper.displayName = "Stepper";
|
|
46
|
+
|
|
47
|
+
interface StepProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
48
|
+
step: number;
|
|
49
|
+
label: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const Step = React.forwardRef<HTMLDivElement, StepProps>(
|
|
54
|
+
({ step, label, description, className, ...props }, ref) => {
|
|
55
|
+
const { currentStep, totalSteps } = useStepper();
|
|
56
|
+
const isActive = step === currentStep;
|
|
57
|
+
const isCompleted = step < currentStep;
|
|
58
|
+
const isFirst = step === 1;
|
|
59
|
+
const isLast = step === totalSteps;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn("flex flex-1 flex-col items-center", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
<div className="flex w-full items-center">
|
|
68
|
+
{step > 1 && (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
"h-0.5 flex-1 transition-colors",
|
|
72
|
+
step <= currentStep ? "bg-primary" : "bg-muted"
|
|
73
|
+
)}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
<div
|
|
77
|
+
className={cn(
|
|
78
|
+
"relative flex h-10 w-10 items-center justify-center rounded-full border-2 transition-colors",
|
|
79
|
+
isActive && "border-primary bg-primary text-primary-foreground",
|
|
80
|
+
isCompleted && "border-primary bg-primary text-primary-foreground",
|
|
81
|
+
!isActive && !isCompleted && "border-muted bg-background text-muted-foreground"
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{isCompleted ? (
|
|
85
|
+
<Check className="h-5 w-5" />
|
|
86
|
+
) : (
|
|
87
|
+
<span>{step}</span>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
{step < totalSteps && (
|
|
91
|
+
<div
|
|
92
|
+
className={cn(
|
|
93
|
+
"h-0.5 flex-1 transition-colors",
|
|
94
|
+
step < currentStep ? "bg-primary" : "bg-muted"
|
|
95
|
+
)}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
<div className={cn(
|
|
100
|
+
"mt-2",
|
|
101
|
+
isFirst && "text-left self-start",
|
|
102
|
+
isLast && "text-right self-end",
|
|
103
|
+
!isFirst && !isLast && "text-center"
|
|
104
|
+
)}>
|
|
105
|
+
<div
|
|
106
|
+
className={cn(
|
|
107
|
+
"transition-colors",
|
|
108
|
+
isActive && "text-foreground",
|
|
109
|
+
!isActive && "text-muted-foreground"
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{label}
|
|
113
|
+
</div>
|
|
114
|
+
{description && (
|
|
115
|
+
<div className="mt-1 text-muted-foreground">
|
|
116
|
+
{description}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
Step.displayName = "Step";
|
|
125
|
+
|
|
126
|
+
export { Stepper, Step, useStepper };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
const Switch = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof SwitchPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<SwitchPrimitive.Root
|
|
13
|
+
className={cn(
|
|
14
|
+
"peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent transition-all outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
15
|
+
"data-[state=checked]:bg-primary",
|
|
16
|
+
"data-[state=unchecked]:bg-muted",
|
|
17
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
ref={ref}
|
|
22
|
+
>
|
|
23
|
+
<SwitchPrimitive.Thumb
|
|
24
|
+
className={cn(
|
|
25
|
+
"pointer-events-none block size-4 rounded-full ring-0 transition-transform shadow-sm",
|
|
26
|
+
"bg-background",
|
|
27
|
+
"data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
|
28
|
+
)}
|
|
29
|
+
/>
|
|
30
|
+
</SwitchPrimitive.Root>
|
|
31
|
+
));
|
|
32
|
+
Switch.displayName = SwitchPrimitive.Root.displayName;
|
|
33
|
+
|
|
34
|
+
export { Switch };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "./utils";
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="table-container"
|
|
11
|
+
className="relative w-full overflow-x-auto"
|
|
12
|
+
>
|
|
13
|
+
<table
|
|
14
|
+
data-slot="table"
|
|
15
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
23
|
+
return (
|
|
24
|
+
<thead
|
|
25
|
+
data-slot="table-header"
|
|
26
|
+
className={cn("[&_tr]:border-b", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
33
|
+
return (
|
|
34
|
+
<tbody
|
|
35
|
+
data-slot="table-body"
|
|
36
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
43
|
+
return (
|
|
44
|
+
<tfoot
|
|
45
|
+
data-slot="table-footer"
|
|
46
|
+
className={cn(
|
|
47
|
+
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
56
|
+
return (
|
|
57
|
+
<tr
|
|
58
|
+
data-slot="table-row"
|
|
59
|
+
className={cn(
|
|
60
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
69
|
+
return (
|
|
70
|
+
<th
|
|
71
|
+
data-slot="table-head"
|
|
72
|
+
className={cn(
|
|
73
|
+
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
82
|
+
return (
|
|
83
|
+
<td
|
|
84
|
+
data-slot="table-cell"
|
|
85
|
+
className={cn(
|
|
86
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function TableCaption({
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<"caption">) {
|
|
98
|
+
return (
|
|
99
|
+
<caption
|
|
100
|
+
data-slot="table-caption"
|
|
101
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
Table,
|
|
109
|
+
TableHeader,
|
|
110
|
+
TableBody,
|
|
111
|
+
TableFooter,
|
|
112
|
+
TableHead,
|
|
113
|
+
TableRow,
|
|
114
|
+
TableCell,
|
|
115
|
+
TableCaption,
|
|
116
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
function Tabs({
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
data-slot="tabs"
|
|
15
|
+
className={cn("flex flex-col gap-2", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function TabsList({
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
25
|
+
return (
|
|
26
|
+
<TabsPrimitive.List
|
|
27
|
+
data-slot="tabs-list"
|
|
28
|
+
className={cn(
|
|
29
|
+
"relative inline-flex h-12 w-fit items-end justify-start border-b border-border bg-transparent",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function TabsTrigger({
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
41
|
+
return (
|
|
42
|
+
<TabsPrimitive.Trigger
|
|
43
|
+
data-slot="tabs-trigger"
|
|
44
|
+
className={cn(
|
|
45
|
+
"relative inline-flex h-12 items-center justify-center gap-1.5 bg-transparent px-4 py-3 text-sm font-medium whitespace-nowrap transition-all duration-200 ease-in-out border-b-2 border-transparent text-muted-foreground hover:text-foreground hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-primary data-[state=active]:border-primary data-[state=active]:bg-transparent [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TabsContent({
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
57
|
+
return (
|
|
58
|
+
<TabsPrimitive.Content
|
|
59
|
+
data-slot="tabs-content"
|
|
60
|
+
className={cn("flex-1 outline-none", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
|
|
5
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
|
|
6
|
+
({ className, ...props }, ref) => {
|
|
7
|
+
return (
|
|
8
|
+
<textarea
|
|
9
|
+
data-slot="textarea"
|
|
10
|
+
className={cn(
|
|
11
|
+
"flex min-h-16 w-full px-3 py-2 text-base bg-background rounded-[var(--radius)] border border-border transition-colors outline-none resize-none text-foreground",
|
|
12
|
+
"placeholder:text-muted-foreground",
|
|
13
|
+
"focus:ring-2 focus:ring-primary focus:border-transparent",
|
|
14
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
15
|
+
"md:text-sm",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
ref={ref}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
Textarea.displayName = "Textarea";
|
|
25
|
+
|
|
26
|
+
export { Textarea };
|