feat: initialize reactjs project using vite

This commit is contained in:
Melvin Ragusa
2026-01-21 22:38:10 +01:00
parent 95ca6f57e7
commit eccc359782
52 changed files with 9556 additions and 116 deletions

View File

@@ -0,0 +1,82 @@
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-sm);
font-family: var(--md-sys-typescale-body-font);
font-weight: 600;
border: none;
border-radius: var(--radius-full);
cursor: pointer;
transition:
background-color var(--transition-fast),
color var(--transition-fast),
box-shadow var(--transition-fast);
}
.button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Sizes */
.sm {
padding: var(--space-sm) var(--space-md);
font-size: 0.875rem;
}
.md {
padding: var(--space-md) var(--space-xl);
font-size: 1rem;
}
.lg {
padding: var(--space-lg) var(--space-2xl);
font-size: 1.125rem;
}
/* Variants */
.primary {
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}
.primary:hover:not(:disabled) {
background-color: var(--md-sys-color-on-primary-container);
box-shadow: 0 4px 20px rgba(127, 217, 152, 0.3);
}
.secondary {
background-color: var(--md-sys-color-surface-container-high);
color: var(--md-sys-color-on-surface);
}
.secondary:hover:not(:disabled) {
background-color: var(--md-sys-color-surface-container-highest);
}
.outline {
background-color: transparent;
color: var(--md-sys-color-primary);
border: 2px solid var(--md-sys-color-primary);
}
.outline:hover:not(:disabled) {
background-color: rgba(127, 217, 152, 0.1);
}
/* Loader */
.loader {
width: 20px;
height: 20px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,42 @@
import { type ReactNode } from 'react';
import { motion } from 'motion/react';
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'outline';
size?: 'sm' | 'md' | 'lg';
children: ReactNode;
isLoading?: boolean;
disabled?: boolean;
className?: string;
type?: 'button' | 'submit' | 'reset';
onClick?: () => void;
}
export function Button({
variant = 'primary',
size = 'md',
children,
isLoading,
disabled,
className,
type = 'button',
onClick,
}: ButtonProps) {
return (
<motion.button
type={type}
className={`${styles.button} ${styles[variant]} ${styles[size]} ${className || ''}`}
disabled={disabled || isLoading}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={onClick}
>
{isLoading ? (
<span className={styles.loader} />
) : (
children
)}
</motion.button>
);
}

View File

@@ -0,0 +1,51 @@
.card {
position: relative;
padding: var(--space-xl);
background-color: var(--md-sys-color-surface-container);
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--radius-lg);
transition:
border-color var(--transition-fast),
box-shadow var(--transition-fast),
background-color var(--transition-fast);
}
.card.hoverable:hover {
border-color: var(--md-sys-color-primary);
background-color: var(--md-sys-color-surface-container-high);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(127, 217, 152, 0.1),
inset 0 1px 0 rgba(127, 217, 152, 0.1);
}
.icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
margin-bottom: var(--space-lg);
background-color: var(--md-sys-color-primary-container);
border-radius: var(--radius-md);
color: var(--md-sys-color-on-primary-container);
}
.icon svg {
width: 28px;
height: 28px;
}
.title {
margin-bottom: var(--space-sm);
font-size: 1.25rem;
font-weight: 600;
color: var(--md-sys-color-on-surface);
}
.description {
font-size: 0.95rem;
line-height: 1.6;
color: var(--md-sys-color-on-surface);
opacity: 0.75;
}

View File

@@ -0,0 +1,49 @@
import { type ReactNode } from 'react';
import { motion } from 'motion/react';
import styles from './Card.module.css';
interface CardProps {
children: ReactNode;
className?: string;
hover?: boolean;
}
export function Card({ children, className, hover = true }: CardProps) {
return (
<motion.div
className={`${styles.card} ${hover ? styles.hoverable : ''} ${className || ''}`}
whileHover={hover ? { y: -4 } : undefined}
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
);
}
interface CardIconProps {
children: ReactNode;
}
export function CardIcon({ children }: CardIconProps) {
return <div className={styles.icon}>{children}</div>;
}
interface CardTitleProps {
children: ReactNode;
}
export function CardTitle({ children }: CardTitleProps) {
return <h3 className={styles.title}>{children}</h3>;
}
interface CardDescriptionProps {
children: ReactNode;
}
export function CardDescription({ children }: CardDescriptionProps) {
return <p className={styles.description}>{children}</p>;
}
Card.Icon = CardIcon;
Card.Title = CardTitle;
Card.Description = CardDescription;

View File

@@ -0,0 +1,58 @@
.field {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.label {
font-size: 0.875rem;
font-weight: 500;
color: var(--md-sys-color-on-surface);
}
.input {
padding: var(--space-md);
font-family: var(--md-sys-typescale-body-font);
font-size: 1rem;
color: var(--md-sys-color-on-surface);
background-color: var(--md-sys-color-surface-container);
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: var(--radius-md);
transition:
border-color var(--transition-fast),
box-shadow var(--transition-fast),
background-color var(--transition-fast);
}
.input::placeholder {
color: var(--md-sys-color-outline);
}
.input:hover {
border-color: var(--md-sys-color-outline);
background-color: var(--md-sys-color-surface-container-high);
}
.input:focus {
outline: none;
border-color: var(--md-sys-color-primary);
box-shadow: 0 0 0 3px rgba(127, 217, 152, 0.15);
}
.textarea {
min-height: 150px;
resize: vertical;
}
.hasError .input {
border-color: var(--md-sys-color-error);
}
.hasError .input:focus {
box-shadow: 0 0 0 3px rgba(255, 180, 171, 0.15);
}
.error {
font-size: 0.8125rem;
color: var(--md-sys-color-error);
}

View File

@@ -0,0 +1,58 @@
import { type InputHTMLAttributes, type TextareaHTMLAttributes, forwardRef } from 'react';
import styles from './Input.module.css';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, id, className, ...props }, ref) => {
const inputId = id || label.toLowerCase().replace(/\s+/g, '-');
return (
<div className={`${styles.field} ${error ? styles.hasError : ''} ${className || ''}`}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
<input
ref={ref}
id={inputId}
className={styles.input}
{...props}
/>
{error && <span className={styles.error}>{error}</span>}
</div>
);
}
);
Input.displayName = 'Input';
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
label: string;
error?: string;
}
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
({ label, error, id, className, ...props }, ref) => {
const inputId = id || label.toLowerCase().replace(/\s+/g, '-');
return (
<div className={`${styles.field} ${error ? styles.hasError : ''} ${className || ''}`}>
<label htmlFor={inputId} className={styles.label}>
{label}
</label>
<textarea
ref={ref}
id={inputId}
className={`${styles.input} ${styles.textarea}`}
{...props}
/>
{error && <span className={styles.error}>{error}</span>}
</div>
);
}
);
Textarea.displayName = 'Textarea';

View File

@@ -0,0 +1,3 @@
export { Button } from './Button';
export { Card } from './Card';
export { Input, Textarea } from './Input';