From 13626d56c23be73bc39c82f4fa5be99569438ee5 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sat, 18 Oct 2025 14:48:07 +0200 Subject: [PATCH] feat: add opacity and blur controls for background settings, enhancing customization options in the UI --- package.json | 1 + pnpm-lock.yaml | 35 ++++++++++ prisma/schema.prisma | 2 +- src/components/layout/ClientLayout.tsx | 9 ++- .../settings/BackgroundSettings.tsx | 68 +++++++++++++++++++ src/components/ui/slider.tsx | 29 ++++++++ src/styles/globals.css | 2 + src/types/preferences.ts | 4 ++ 8 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/components/ui/slider.tsx diff --git a/package.json b/package.json index 922e113..6afc162 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-toast": "1.2.15", "@types/bcryptjs": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a293e50..f600a6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@radix-ui/react-select': specifier: ^2.1.6 version: 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slider': + specifier: ^1.3.6 + version: 1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slot': specifier: 1.2.3 version: 1.2.3(@types/react@19.2.2)(react@19.2.0) @@ -850,6 +853,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -3634,6 +3650,25 @@ snapshots: '@types/react': 19.2.2 '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bdb6c0c..de68aeb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -66,7 +66,7 @@ model Preferences { cacheMode String @default("memory") // "memory" | "file" showOnlyUnread Boolean @default(false) displayMode Json @default("{\"compact\": false, \"itemsPerPage\": 20}") - background Json @default("{\"type\": \"default\"}") + background Json @default("{\"type\": \"default\", \"opacity\": 100, \"blur\": 0}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/components/layout/ClientLayout.tsx b/src/components/layout/ClientLayout.tsx index 5d77a4e..1211613 100644 --- a/src/components/layout/ClientLayout.tsx +++ b/src/components/layout/ClientLayout.tsx @@ -29,10 +29,12 @@ export default function ClientLayout({ children, initialLibraries = [], initialF const backgroundStyle = useMemo(() => { const bg = preferences.background; + const blur = bg.blur || 0; if (bg.type === "gradient" && bg.gradient) { return { backgroundImage: bg.gradient, + filter: blur > 0 ? `blur(${blur}px)` : undefined, }; } @@ -42,6 +44,7 @@ export default function ClientLayout({ children, initialLibraries = [], initialF backgroundSize: "cover" as const, backgroundPosition: "center" as const, backgroundRepeat: "no-repeat" as const, + filter: blur > 0 ? `blur(${blur}px)` : undefined, }; } @@ -90,6 +93,7 @@ export default function ClientLayout({ children, initialLibraries = [], initialF const isPublicRoute = publicRoutes.includes(pathname) || pathname.startsWith('/books/'); const hasCustomBackground = preferences.background.type === "gradient" || preferences.background.type === "image"; + const contentOpacity = (preferences.background.opacity || 100) / 100; return ( @@ -100,7 +104,10 @@ export default function ClientLayout({ children, initialLibraries = [], initialF style={backgroundStyle} /> )} -
+
{!isPublicRoute &&
} {!isPublicRoute && ( { + try { + await updatePreferences({ + background: { + ...preferences.background, + opacity: value[0], + }, + }); + } catch (error) { + console.error("Erreur:", error); + } + }; + + const handleBlurChange = async (value: number[]) => { + try { + await updatePreferences({ + background: { + ...preferences.background, + blur: value[0], + }, + }); + } catch (error) { + console.error("Erreur:", error); + } + }; + return ( @@ -184,6 +211,47 @@ export function BackgroundSettings() {

)} + + {/* Contrôles d'opacité et de flou */} + {(preferences.background.type === "gradient" || preferences.background.type === "image") && ( + <> +
+
+ + {preferences.background.opacity || 100}% +
+ +

+ Contrôle la transparence du contenu par rapport au background +

+
+ +
+
+ + {preferences.background.blur || 0}px +
+ +

+ Applique un effet de flou au background +

+
+ + )}
diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx new file mode 100644 index 0000000..fa20878 --- /dev/null +++ b/src/components/ui/slider.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SliderPrimitive from "@radix-ui/react-slider" + +import { cn } from "@/lib/utils" + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider } + diff --git a/src/styles/globals.css b/src/styles/globals.css index e7a0370..41fbc09 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -22,6 +22,7 @@ body { @layer base { :root { --background: 0 0% 100%; + --background-rgb: 255, 255, 255; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; @@ -54,6 +55,7 @@ body { .dark { --background: 222.2 84% 4.9%; + --background-rgb: 12, 17, 29; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; diff --git a/src/types/preferences.ts b/src/types/preferences.ts index 72ea553..227e88a 100644 --- a/src/types/preferences.ts +++ b/src/types/preferences.ts @@ -4,6 +4,8 @@ export interface BackgroundPreferences { type: BackgroundType; gradient?: string; imageUrl?: string; + opacity?: number; // 0-100 + blur?: number; // 0-20 (px) } export interface UserPreferences { @@ -27,6 +29,8 @@ export const defaultPreferences: UserPreferences = { }, background: { type: "default", + opacity: 100, + blur: 0, }, };