mirror of
https://github.com/guezoloic/website.git
synced 2026-03-31 03:01:37 +00:00
feat: rework entire project structure
This commit is contained in:
38
app/components/ui/Button.tsx
Normal file
38
app/components/ui/Button.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type ButtonProps = {
|
||||
children: ReactNode;
|
||||
onClick?: () => void;
|
||||
label: string;
|
||||
variant?: "icon" | "text";
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Button({ children, onClick, label, variant = "icon", className = "" }: ButtonProps) {
|
||||
const BASECLASS = "cursor-pointer flex items-center justify-center backdrop-blur-sm \
|
||||
bg-black/17 shadow-md text-white transition-all duration-200 ease-out \
|
||||
hover:bg-white/15 active:scale-95 shadow-lg shadow-black/50 \
|
||||
pointer-events-auto hover:shadow-black/0";
|
||||
|
||||
|
||||
// dictionary to choose if it's a icon or text button
|
||||
const variants: Record<typeof variant, string> = {
|
||||
icon: "rounded-full w-12 h-12 md:w-14 md:h-14 hover:scale-110",
|
||||
text: "rounded-3xl px-4 h-12 md:h-14 md:px-6 max-w-max hover:scale-105",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<button onClick={onClick} aria-label={label} className={`${BASECLASS} ${variants[variant]}`}>
|
||||
{children}
|
||||
</button>
|
||||
{label && (
|
||||
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1
|
||||
rounded-md text-xs text-white bg-black/80 opacity-0
|
||||
group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/components/ui/LangSwitcher.tsx
Normal file
22
app/components/ui/LangSwitcher.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import Button from '@app/components/ui/Button';
|
||||
|
||||
import i18n from "@app/lib/i18n";
|
||||
|
||||
export default function Lang() {
|
||||
const toggleLanguage = () => {
|
||||
const newLang = i18n.language === "fr" ? "en" : "fr";
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
|
||||
const nextLangLabel = i18n.language === "fr" ? "EN" : "FR";
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={toggleLanguage}
|
||||
label={`Lang: ${nextLangLabel}`}
|
||||
variant="icon"
|
||||
>
|
||||
{nextLangLabel}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
43
app/components/ui/Window.tsx
Normal file
43
app/components/ui/Window.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { ReactNode, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface SectionProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function Window ({ open, onClose, children }: SectionProps) {
|
||||
useEffect(() => {
|
||||
if (open) document.body.style.overflow = "hidden";
|
||||
else document.body.style.overflow = "";
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
className="fixed inset-0 z-10 bg-black/30 backdrop-blur-sm"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: 0.3
|
||||
}}
|
||||
>
|
||||
<div className="h-full overflow-y-auto text-white">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 40 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col md:flex-row items-center justify-center gap-10 px-6 md:px-24 py-20 md:py-32"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user