first commit

This commit is contained in:
gestions-UO
2026-05-11 20:50:44 +02:00
parent f7ba09d405
commit 838bec205e
36 changed files with 2514 additions and 99 deletions

26
components/ui/Badge.tsx Normal file
View File

@@ -0,0 +1,26 @@
"use client";
import { motion } from "framer-motion";
import { ReactNode } from "react";
interface BadgeProps {
children: ReactNode;
}
/**
* Componente Badge para etiquetas de sección con estilo Flat Premium.
*
*/
export const Badge = ({ children }: BadgeProps) => {
return (
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
className="inline-block bg-ice/30 px-4 py-1.5 rounded-full mb-8 border border-ice"
>
<span className="text-[12px] font-bold tracking-[0.1em] uppercase text-ocean">
{children}
</span>
</motion.div>
);
};

View File

@@ -0,0 +1,9 @@
import { ReactNode } from "react";
export const BentoGrid = ({ children, className = "" }: { children: ReactNode; className?: string }) => {
return (
<div className={`grid grid-cols-1 md:grid-cols-3 gap-6 ${className}`}>
{children}
</div>
);
};

44
components/ui/Button.tsx Normal file
View File

@@ -0,0 +1,44 @@
"use client";
import { motion, HTMLMotionProps } from "framer-motion";
import { ReactNode } from "react";
// Extendemos de HTMLMotionProps para heredar todos los atributos nativos de un botón (incluyendo disabled)
interface ButtonProps extends HTMLMotionProps<"button"> {
children: ReactNode;
variant?: "primary" | "outline" | "ocean";
className?: string;
}
export const Button = ({
children,
variant = "primary",
className = "",
disabled,
...props
}: ButtonProps) => {
const variants = {
primary: "bg-navy text-white hover:bg-ocean disabled:bg-navy/50",
outline: "border-2 border-navy text-navy hover:bg-navy hover:text-white disabled:opacity-50",
ocean: "bg-ocean text-white hover:bg-sky disabled:bg-ocean/50",
};
return (
<motion.button
// Los botones en sistemas tipo Together AI tienen interacciones táctiles sutiles
whileHover={disabled ? {} : { scale: 1.02 }}
whileTap={disabled ? {} : { scale: 0.98 }}
disabled={disabled}
className={`
px-8 py-4 rounded-full font-bold text-[14px]
transition-all duration-300 uppercase tracking-tight
disabled:cursor-not-allowed
${variants[variant]}
${className}
`}
{...props}
>
{children}
</motion.button>
);
};

49
components/ui/Card.tsx Normal file
View File

@@ -0,0 +1,49 @@
"use client";
import { motion } from "framer-motion";
import { ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import { clsx, type ClassValue } from "clsx";
// Utilidad para unir clases de Tailwind
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
interface CardProps {
children: ReactNode;
className?: string;
delay?: number;
variant?: "white" | "ice" | "navy" | "ocean" | "none";
}
export const Card = ({
children,
className = "",
delay = 0,
variant = "white"
}: CardProps) => {
const variants = {
white: "bg-white border-crisp hover:border-sky",
ice: "bg-ice/20 border-crisp hover:bg-ice/40",
navy: "bg-navy border-navy text-white",
ocean: "bg-ocean border-ocean text-white",
none: "border-crisp"
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay, ease: [0.22, 1, 0.36, 1] }}
className={cn(
"rounded-[32px] border p-10 flex flex-col transition-all duration-500",
variants[variant],
className
)}
>
{children}
</motion.div>
);
};

View File

@@ -0,0 +1,36 @@
"use client";
import React, { forwardRef } from "react";
interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
({ label, error, className = "", ...props }, ref) => {
return (
<div className="flex flex-col gap-2">
<div className="flex items-start gap-3">
<input
type="checkbox"
ref={ref}
className={`
mt-1 w-4 h-4 rounded border-crisp text-ocean
focus:ring-ocean focus:ring-offset-0 cursor-pointer
${className}
`}
{...props}
/>
<label className="text-[13px] text-navy/60 font-medium leading-tight cursor-pointer">
{label}
</label>
</div>
{error && (
<span className="text-red-500 text-xs font-bold">{error}</span>
)}
</div>
);
}
);
Checkbox.displayName = "Checkbox";

38
components/ui/Input.tsx Normal file
View File

@@ -0,0 +1,38 @@
"use client";
import * as React from "react";
import { cn } from "@/lib/utils"; // Ahora sí existe
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, label, error, type, ...props }, ref) => {
return (
<div className="w-full flex flex-col gap-2">
{label && (
<label className="text-[13px] font-extrabold text-navy uppercase tracking-widest">
{label}
</label>
)}
<input
type={type}
ref={ref}
className={cn(
// Clases base: Flat Premium
"w-full bg-ice/10 border rounded-[12px] px-4 py-3.5 text-[15px] font-medium text-navy transition-all duration-200",
"placeholder:text-navy/30 focus:bg-white focus:border-ocean",
// MATAMOS LA LÍNEA AZUL AQUÍ:
"outline-none ring-0 focus:ring-0 focus:ring-offset-0 ring-offset-transparent",
error ? "border-red-500" : "border-crisp",
className
)}
{...props}
/>
{error && <span className="text-red-500 text-xs font-bold">{error}</span>}
</div>
);
}
);
Input.displayName = "Input";

View File

@@ -0,0 +1,34 @@
"use client";
import React, { forwardRef } from "react";
interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
label: string;
error?: string;
}
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
({ label, error, className = "", ...props }, ref) => {
return (
<div className="flex flex-col gap-2 w-full">
<label className="text-[13px] font-extrabold text-navy uppercase tracking-widest">
{label}
</label>
<textarea
ref={ref}
className={`
w-full bg-ice/10 border rounded-[12px] px-4 py-3.5 text-[15px]
font-medium text-navy transition-all duration-200 resize-none
placeholder:text-navy/30 outline-none
focus:border-ocean focus:bg-white
${error ? "border-red-500" : "border-crisp"}
${className}
`}
{...props}
/>
{error && <span className="text-red-500 text-xs font-bold">{error}</span>}
</div>
);
}
);
Textarea.displayName = "Textarea";