first commit
This commit is contained in:
26
components/ui/Badge.tsx
Normal file
26
components/ui/Badge.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
9
components/ui/BentoGrid.tsx
Normal file
9
components/ui/BentoGrid.tsx
Normal 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
44
components/ui/Button.tsx
Normal 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
49
components/ui/Card.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
36
components/ui/Checkbox.tsx
Normal file
36
components/ui/Checkbox.tsx
Normal 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
38
components/ui/Input.tsx
Normal 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";
|
||||
34
components/ui/Textarea.tsx
Normal file
34
components/ui/Textarea.tsx
Normal 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";
|
||||
Reference in New Issue
Block a user