-
- {t("settings.display.title")}
-
-
{t("settings.display.description")}
+
+
+ {t("settings.display.title")}
+ {t("settings.display.description")}
+
+
+
+
+
+
+ {t("settings.display.thumbnails.description")}
+
+
+
-
-
-
-
-
-
- {t("settings.display.thumbnails.description")}
-
-
-
-
-
-
-
-
- {t("settings.display.unreadFilter.description")}
-
-
-
{
- try {
- await updatePreferences({ showOnlyUnread: checked });
- toast({
- title: t("settings.title"),
- description: t("settings.komga.messages.configSaved"),
- });
- } catch (error) {
- console.error("Erreur détaillée:", error);
- toast({
- variant: "destructive",
- title: t("settings.error.title"),
- description: t("settings.error.message"),
- });
- }
- }}
- />
-
-
-
-
-
- {t("settings.display.debugMode.description")}
-
-
-
{
- try {
- await updatePreferences({ debug: checked });
- toast({
- title: t("settings.title"),
- description: t("settings.komga.messages.configSaved"),
- });
- } catch (error) {
- console.error("Erreur détaillée:", error);
- toast({
- variant: "destructive",
- title: t("settings.error.title"),
- description: t("settings.error.message"),
- });
- }
- }}
- />
+
+
+
+
+ {t("settings.display.unreadFilter.description")}
+
+
{
+ try {
+ await updatePreferences({ showOnlyUnread: checked });
+ toast({
+ title: t("settings.title"),
+ description: t("settings.komga.messages.configSaved"),
+ });
+ } catch (error) {
+ console.error("Erreur détaillée:", error);
+ toast({
+ variant: "destructive",
+ title: t("settings.error.title"),
+ description: t("settings.error.message"),
+ });
+ }
+ }}
+ />
-
-
+
+
+
+
+ {t("settings.display.debugMode.description")}
+
+
+
{
+ try {
+ await updatePreferences({ debug: checked });
+ toast({
+ title: t("settings.title"),
+ description: t("settings.komga.messages.configSaved"),
+ });
+ } catch (error) {
+ console.error("Erreur détaillée:", error);
+ toast({
+ variant: "destructive",
+ title: t("settings.error.title"),
+ description: t("settings.error.message"),
+ });
+ }
+ }}
+ />
+
+
+
);
}
diff --git a/src/components/settings/KomgaSettings.tsx b/src/components/settings/KomgaSettings.tsx
index 47e7275..6f64620 100644
--- a/src/components/settings/KomgaSettings.tsx
+++ b/src/components/settings/KomgaSettings.tsx
@@ -5,6 +5,7 @@ import { useTranslate } from "@/hooks/useTranslate";
import { useToast } from "@/components/ui/use-toast";
import { Network, Loader2 } from "lucide-react";
import type { KomgaConfig } from "@/types/komga";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
interface KomgaSettingsProps {
initialConfig: KomgaConfig | null;
@@ -144,15 +145,15 @@ export function KomgaSettings({ initialConfig }: KomgaSettingsProps) {
};
return (
-
-
-
-
-
- {t("settings.komga.title")}
-
-
{t("settings.komga.description")}
-
+
+
+
+
+ {t("settings.komga.title")}
+
+ {t("settings.komga.description")}
+
+
{!shouldShowForm ? (
@@ -274,7 +275,7 @@ export function KomgaSettings({ initialConfig }: KomgaSettingsProps) {
)}
-
-
+
+
);
}
diff --git a/src/components/ui/container.tsx b/src/components/ui/container.tsx
new file mode 100644
index 0000000..8d0f2fd
--- /dev/null
+++ b/src/components/ui/container.tsx
@@ -0,0 +1,47 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/lib/utils";
+
+const containerVariants = cva("mx-auto px-4 sm:px-6 lg:px-8", {
+ variants: {
+ size: {
+ default: "max-w-screen-2xl",
+ narrow: "max-w-4xl",
+ wide: "max-w-screen-3xl",
+ full: "max-w-full",
+ },
+ spacing: {
+ none: "",
+ sm: "py-4",
+ default: "py-8",
+ lg: "py-12",
+ },
+ },
+ defaultVariants: {
+ size: "default",
+ spacing: "default",
+ },
+});
+
+export interface ContainerProps
+ extends React.HTMLAttributes
,
+ VariantProps {
+ as?: React.ElementType;
+}
+
+const Container = React.forwardRef(
+ ({ className, size, spacing, as: Component = "div", ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+
+Container.displayName = "Container";
+
+export { Container, containerVariants };
+
diff --git a/src/components/ui/icon-button.tsx b/src/components/ui/icon-button.tsx
new file mode 100644
index 0000000..3a79c0f
--- /dev/null
+++ b/src/components/ui/icon-button.tsx
@@ -0,0 +1,33 @@
+import * as React from "react";
+import { type LucideIcon } from "lucide-react";
+import { Button, type ButtonProps } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+
+export interface IconButtonProps extends Omit {
+ icon: LucideIcon;
+ label?: string;
+ tooltip?: string;
+ iconClassName?: string;
+}
+
+const IconButton = React.forwardRef(
+ ({ icon: Icon, label, tooltip, iconClassName, className, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+
+IconButton.displayName = "IconButton";
+
+export { IconButton };
+
diff --git a/src/components/ui/nav-button.tsx b/src/components/ui/nav-button.tsx
new file mode 100644
index 0000000..68dcf47
--- /dev/null
+++ b/src/components/ui/nav-button.tsx
@@ -0,0 +1,39 @@
+import * as React from "react";
+import { type LucideIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+export interface NavButtonProps extends React.ButtonHTMLAttributes {
+ icon: LucideIcon;
+ label: string;
+ active?: boolean;
+ count?: number;
+}
+
+const NavButton = React.forwardRef(
+ ({ icon: Icon, label, active, count, className, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+
+NavButton.displayName = "NavButton";
+
+export { NavButton };
+
diff --git a/src/components/ui/scroll-container.tsx b/src/components/ui/scroll-container.tsx
new file mode 100644
index 0000000..3849b25
--- /dev/null
+++ b/src/components/ui/scroll-container.tsx
@@ -0,0 +1,105 @@
+"use client";
+
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+export interface ScrollContainerProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+ showArrows?: boolean;
+ scrollAmount?: number;
+ arrowLeftLabel?: string;
+ arrowRightLabel?: string;
+}
+
+const ScrollContainer = React.forwardRef(
+ (
+ {
+ children,
+ className,
+ showArrows = true,
+ scrollAmount = 400,
+ arrowLeftLabel = "Scroll left",
+ arrowRightLabel = "Scroll right",
+ ...props
+ },
+ ref
+ ) => {
+ const scrollContainerRef = React.useRef(null);
+ const [showLeftArrow, setShowLeftArrow] = React.useState(false);
+ const [showRightArrow, setShowRightArrow] = React.useState(true);
+
+ const handleScroll = React.useCallback(() => {
+ if (!scrollContainerRef.current) return;
+
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
+ setShowLeftArrow(scrollLeft > 0);
+ setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 10);
+ }, []);
+
+ const scroll = React.useCallback(
+ (direction: "left" | "right") => {
+ if (!scrollContainerRef.current) return;
+
+ const scrollValue = direction === "left" ? -scrollAmount : scrollAmount;
+ scrollContainerRef.current.scrollBy({ left: scrollValue, behavior: "smooth" });
+ },
+ [scrollAmount]
+ );
+
+ React.useEffect(() => {
+ const container = scrollContainerRef.current;
+ if (!container) return;
+
+ handleScroll();
+
+ const resizeObserver = new ResizeObserver(handleScroll);
+ resizeObserver.observe(container);
+
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, [handleScroll]);
+
+ return (
+
+ {showArrows && showLeftArrow && (
+
+ )}
+
+
+ {children}
+
+
+ {showArrows && showRightArrow && (
+
+ )}
+
+ );
+ }
+);
+
+ScrollContainer.displayName = "ScrollContainer";
+
+export { ScrollContainer };
+
diff --git a/src/components/ui/section.tsx b/src/components/ui/section.tsx
new file mode 100644
index 0000000..ce907e2
--- /dev/null
+++ b/src/components/ui/section.tsx
@@ -0,0 +1,45 @@
+import * as React from "react";
+import { type LucideIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+export interface SectionProps extends React.HTMLAttributes {
+ title?: string;
+ icon?: LucideIcon;
+ description?: string;
+ actions?: React.ReactNode;
+ headerClassName?: string;
+}
+
+const Section = React.forwardRef(
+ (
+ { title, icon: Icon, description, actions, children, className, headerClassName, ...props },
+ ref
+ ) => {
+ return (
+
+ {(title || actions) && (
+
+ {title && (
+
+ {Icon &&
}
+
+
{title}
+ {description && (
+
{description}
+ )}
+
+
+ )}
+ {actions &&
{actions}
}
+
+ )}
+ {children}
+
+ );
+ }
+);
+
+Section.displayName = "Section";
+
+export { Section };
+
diff --git a/src/components/ui/status-badge.tsx b/src/components/ui/status-badge.tsx
new file mode 100644
index 0000000..551a11c
--- /dev/null
+++ b/src/components/ui/status-badge.tsx
@@ -0,0 +1,44 @@
+import * as React from "react";
+import { type LucideIcon } from "lucide-react";
+import { Badge, type BadgeProps } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+import { cva, type VariantProps } from "class-variance-authority";
+
+const statusBadgeVariants = cva("flex items-center gap-1", {
+ variants: {
+ status: {
+ success: "bg-green-500/10 text-green-500 border-green-500/20",
+ warning: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20",
+ error: "bg-red-500/10 text-red-500 border-red-500/20",
+ info: "bg-blue-500/10 text-blue-500 border-blue-500/20",
+ reading: "bg-blue-500/10 text-blue-500 border-blue-500/20",
+ unread: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20",
+ },
+ },
+ defaultVariants: {
+ status: "info",
+ },
+});
+
+export interface StatusBadgeProps
+ extends Omit,
+ VariantProps {
+ icon?: LucideIcon;
+ children: React.ReactNode;
+}
+
+const StatusBadge = ({ status, icon: Icon, children, className, ...props }: StatusBadgeProps) => {
+ return (
+
+ {Icon && }
+ {children}
+
+ );
+};
+
+export { StatusBadge, statusBadgeVariants };
+