Ders İçeriği

CSS Modülleri

CSS Modülleri, CSS sınıf isimlerini yerel olarak kapsülleyen bir tekniktir. Bu yaklaşım, CSS sınıf isimlerinin otomatik olarak benzersiz hale getirilmesini sağlar ve böylece stil çakışmalarını önler. React uygulamalarında CSS Modülleri kullanmak, bileşen tabanlı mimariye çok uygun bir yaklaşımdır.

CSS Modülleri'nin temel avantajları şunlardır: sınıf ismi çakışmalarının otomatik olarak önlenmesi, bileşen bazında stil kapsülleme, CSS'in JavaScript ile entegrasyonu ve build zamanında optimizasyon imkanları. Bu özellikler, özellikle büyük projelerde CSS yönetimini çok daha kolay hale getirir.

Temel CSS Modülleri Kullanımı

CSS Modülleri kullanmak için dosya adınızın .module.css ile bitmesi gerekir. Create React App, CSS Modülleri'ni varsayılan olarak destekler.

/* Button.module.css */

.button {

  padding: 12px 24px;

  border: none;

  border-radius: 6px;

  font-weight: 600;

  cursor: pointer;

  transition: all 0.2s ease;

  font-size: 16px;

}


.primary {

  background-color: #007bff;

  color: white;

}


.primary:hover {

  background-color: #0056b3;

  transform: translateY(-1px);

  box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);

}


.secondary {

  background-color: #6c757d;

  color: white;

}


.secondary:hover {

  background-color: #545b62;

}


.outline {

  background-color: transparent;

  border: 2px solid #007bff;

  color: #007bff;

}


.outline:hover {

  background-color: #007bff;

  color: white;

}


.large {

  padding: 16px 32px;

  font-size: 18px;

}


.small {

  padding: 8px 16px;

  font-size: 14px;

}


.disabled {

  opacity: 0.6;

  cursor: not-allowed;

  pointer-events: none;

}


.loading {

  position: relative;

  color: transparent;

}


.loading::after {

  content: '';

  position: absolute;

  top: 50%;

  left: 50%;

  transform: translate(-50%, -50%);

  width: 16px;

  height: 16px;

  border: 2px solid currentColor;

  border-top-color: transparent;

  border-radius: 50%;

  animation: spin 1s linear infinite;

}


@keyframes spin {

  to {

    transform: translate(-50%, -50%) rotate(360deg);

  }

}

// Button.js

import React from 'react';

import styles from './Button.module.css';


function Button({ 

  children, 

  variant = 'primary', 

  size = 'medium', 

  disabled = false,

  loading = false,

  onClick,

  ...props 

}) {

  const buttonClasses = [

    styles.button,

    styles[variant],

    styles[size],

    disabled && styles.disabled,

    loading && styles.loading

  ].filter(Boolean).join(' ');

  

  return (

    <button

      className={buttonClasses}

      disabled={disabled || loading}

      onClick={onClick}

      {...props}

    >

      {children}

    </button>

  );

}


export default Button;


// Kullanım örneği

function App() {

  const [loading, setLoading] = useState(false);

  

  const handleClick = async () => {

    setLoading(true);

    await new Promise(resolve => setTimeout(resolve, 2000));

    setLoading(false);

  };

  

  return (

    <div>

      <Button variant="primary" size="large" onClick={handleClick} loading={loading}>

        Primary Large Button

      </Button>

      

      <Button variant="secondary" size="small">

        Secondary Small Button

      </Button>

      

      <Button variant="outline">

        Outline Button

      </Button>

      

      <Button disabled>

        Disabled Button

      </Button>

    </div>

  );

}

Gelişmiş CSS Modülleri Teknikleri

CSS Modülleri ile daha karmaşık stil yönetimi yapabilirsiniz:

/* Card.module.css */

.card {

  background: white;

  border-radius: 12px;

  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  overflow: hidden;

  transition: all 0.3s ease;

}


.card:hover {

  transform: translateY(-4px);

  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);

}


.header {

  padding: 20px;

  border-bottom: 1px solid #e9ecef;

}


.title {

  margin: 0 0 8px 0;

  font-size: 1.25rem;

  font-weight: 600;

  color: #212529;

}


.subtitle {

  margin: 0;

  font-size: 0.875rem;

  color: #6c757d;

}


.body {

  padding: 20px;

}


.footer {

  padding: 20px;

  background-color: #f8f9fa;

  border-top: 1px solid #e9ecef;

}


/* Responsive design */

@media (max-width: 768px) {

  .card {

    margin: 10px;

    border-radius: 8px;

  }

  

  .header,

  .body,

  .footer {

    padding: 15px;

  }

  

  .title {

    font-size: 1.1rem;

  }

}


/* Theme variants */

.dark {

  background: #343a40;

  color: white;

}


.dark .header {

  border-bottom-color: #495057;

}


.dark .title {

  color: white;

}


.dark .subtitle {

  color: #adb5bd;

}


.dark .footer {

  background-color: #495057;

  border-top-color: #6c757d;

}


/* Size variants */

.compact .header,

.compact .body,

.compact .footer {

  padding: 12px;

}


.compact .title {

  font-size: 1rem;

}


.expanded .header,

.expanded .body,

.expanded .footer {

  padding: 30px;

}


.expanded .title {

  font-size: 1.5rem;

}

// Card.js

import React from 'react';

import styles from './Card.module.css';


function Card({ 

  title, 

  subtitle, 

  children, 

  footer, 

  theme = 'light', 

  size = 'normal',

  className,

  ...props 

}) {

  const cardClasses = [

    styles.card,

    styles[theme],

    styles[size],

    className

  ].filter(Boolean).join(' ');

  

  return (

    <div className={cardClasses} {...props}>

      {(title || subtitle) && (

        <div className={styles.header}>

          {title && <h3 className={styles.title}>{title}</h3>}

          {subtitle && <p className={styles.subtitle}>{subtitle}</p>}

        </div>

      )}

      

      {children && (

        <div className={styles.body}>

          {children}

        </div>

      )}

      

      {footer && (

        <div className={styles.footer}>

          {footer}

        </div>

      )}

    </div>

  );

}


export default Card;


// Kullanım

function ProductCard({ product }) {

  return (

    <Card

      title={product.name}

      subtitle={`$${product.price}`}

      footer={

        <div style={{ display: 'flex', gap: '10px' }}>

          <Button variant="primary" size="small">Add to Cart</Button>

          <Button variant="outline" size="small">View Details</Button>

        </div>

      }

    >

      <img 

        src={product.image} 

        alt={product.name}

        style={{ width: '100%', height: '200px', objectFit: 'cover' }}

      />

      <p>{product.description}</p>

    </Card>

  );

}

Styled Components

Styled Components, CSS-in-JS yaklaşımının popüler bir implementasyonudur. JavaScript içinde CSS yazmanıza olanak tanır ve bileşen tabanlı stil yönetimi sağlar. Styled Components'in temel avantajları arasında dinamik stilleme, tema desteği, otomatik vendor prefixing ve dead code elimination bulunur.

Kurulum ve Temel Kullanım

bash npm install styled-components

import styled from 'styled-components';


// Temel styled component

const Button = styled.button`

  background: ${props => props.primary ? '#007bff' : '#6c757d'};

  color: white;

  font-size: 1rem;

  margin: 0.5rem;

  padding: 0.75rem 1.5rem;

  border: none;

  border-radius: 6px;

  cursor: pointer;

  transition: all 0.2s ease;

  

  &:hover {

    background: ${props => props.primary ? '#0056b3' : '#545b62'};

    transform: translateY(-1px);

  }

  

  &:active {

    transform: translateY(0);

  }

  

  &:disabled {

    opacity: 0.6;

    cursor: not-allowed;

    transform: none;

  }

`;


// Props ile dinamik stilleme

const Container = styled.div`

  max-width: ${props => props.fluid ? '100%' : '1200px'};

  margin: 0 auto;

  padding: ${props => props.padding || '20px'};

  background: ${props => props.background || 'transparent'};

  

  @media (max-width: 768px) {

    padding: ${props => props.padding ? `calc(${props.padding} / 2)` : '10px'};

  }

`;


// Mevcut component'i extend etme

const PrimaryButton = styled(Button)`

  background: linear-gradient(45deg, #007bff, #0056b3);

  box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);

  

  &:hover {

    box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);

  }

`;


// Kullanım

function App() {

  return (

    <Container padding="40px" background="#f8f9fa">

      <h1>Styled Components Example</h1>

      <Button primary>Primary Button</Button>

      <Button>Secondary Button</Button>

      <PrimaryButton>Enhanced Primary Button</PrimaryButton>

    </Container>

  );

}

Tema Sistemi

Styled Components ile güçlü tema sistemleri oluşturabilirsiniz:

import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';


// Tema tanımları

const lightTheme = {

  colors: {

    primary: '#007bff',

    secondary: '#6c757d',

    success: '#28a745',

    danger: '#dc3545',

    warning: '#ffc107',

    info: '#17a2b8',

    light: '#f8f9fa',

    dark: '#343a40',

    background: '#ffffff',

    surface: '#f8f9fa',

    text: '#212529',

    textSecondary: '#6c757d',

    border: '#dee2e6'

  },

  spacing: {

    xs: '4px',

    sm: '8px',

    md: '16px',

    lg: '24px',

    xl: '32px',

    xxl: '48px'

  },

  borderRadius: {

    sm: '4px',

    md: '6px',

    lg: '8px',

    xl: '12px',

    round: '50%'

  },

  shadows: {

    sm: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',

    md: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',

    lg: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',

    xl: '0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)'

  },

  breakpoints: {

    sm: '576px',

    md: '768px',

    lg: '992px',

    xl: '1200px'

  }

};


const darkTheme = {

  ...lightTheme,

  colors: {

    ...lightTheme.colors,

    primary: '#0d6efd',

    background: '#121212',

    surface: '#1e1e1e',

    text: '#ffffff',

    textSecondary: '#adb5bd',

    border: '#495057'

  }

};


// Global stiller

const GlobalStyle = createGlobalStyle`

  * {

    margin: 0;

    padding: 0;

    box-sizing: border-box;

  }

  

  body {

    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;

    background-color: ${props => props.theme.colors.background};

    color: ${props => props.theme.colors.text};

    transition: background-color 0.3s ease, color 0.3s ease;

  }

  

  * {

    scrollbar-width: thin;

    scrollbar-color: ${props => props.theme.colors.border} transparent;

  }

  

  *::-webkit-scrollbar {

    width: 8px;

  }

  

  *::-webkit-scrollbar-track {

    background: transparent;

  }

  

  *::-webkit-scrollbar-thumb {

    background-color: ${props => props.theme.colors.border};

    border-radius: 4px;

  }

`;


// Tema kullanan bileşenler

const Card = styled.div`

  background: ${props => props.theme.colors.surface};

  border: 1px solid ${props => props.theme.colors.border};

  border-radius: ${props => props.theme.borderRadius.lg};

  padding: ${props => props.theme.spacing.lg};

  margin: ${props => props.theme.spacing.md};

  box-shadow: ${props => props.theme.shadows.sm};

  transition: all 0.3s ease;

  

  &:hover {

    box-shadow: ${props => props.theme.shadows.md};

    transform: translateY(-2px);

  }

  

  @media (max-width: ${props => props.theme.breakpoints.md}) {

    margin: ${props => props.theme.spacing.sm};

    padding: ${props => props.theme.spacing.md};

  }

`;


const Title = styled.h2`

  color: ${props => props.theme.colors.text};

  margin-bottom: ${props => props.theme.spacing.md};

  font-size: 1.5rem;

  font-weight: 600;

`;


const Text = styled.p`

  color: ${props => props.theme.colors.textSecondary};

  line-height: 1.6;

  margin-bottom: ${props => props.theme.spacing.sm};

`;


const ThemeButton = styled.button`

  background: ${props => props.theme.colors.primary};

  color: white;

  border: none;

  padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};

  border-radius: ${props => props.theme.borderRadius.md};

  cursor: pointer;

  font-weight: 500;

  transition: all 0.2s ease;

  

  &:hover {

    opacity: 0.9;

    transform: translateY(-1px);

  }

`;


// Ana uygulama

function App() {

  const [isDark, setIsDark] = useState(false);

  

  const toggleTheme = () => {

    setIsDark(!isDark);

  };

  

  return (

    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>

      <GlobalStyle />

      <div style={{ minHeight: '100vh', padding: '20px' }}>

        <div style={{ textAlign: 'center', marginBottom: '40px' }}>

          <ThemeButton onClick={toggleTheme}>

            {isDark ? 'Light Theme' : 'Dark Theme'} Geç

          </ThemeButton>

        </div>

        

        <div style={{ 

          display: 'grid', 

          gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',

          gap: '20px',

          maxWidth: '1200px',

          margin: '0 auto'

        }}>

          <Card>

            <Title>Card Title 1</Title>

            <Text>

              Bu bir örnek card içeriğidir. Tema sistemi sayesinde 

              otomatik olarak doğru renkler kullanılır.

            </Text>

            <ThemeButton>Action Button</ThemeButton>

          </Card>

          

          <Card>

            <Title>Card Title 2</Title>

            <Text>

              Styled Components ile tema yönetimi çok kolay ve etkilidir.

              Tüm bileşenler tema değişikliklerini otomatik olarak algılar.

            </Text>

            <ThemeButton>Another Button</ThemeButton>

          </Card>

          

          <Card>

            <Title>Card Title 3</Title>

            <Text>

              Responsive tasarım da tema sistemi ile entegre edilebilir.

              Breakpoint'ler tema objesinde tanımlanır.

            </Text>

            <ThemeButton>Third Button</ThemeButton>

          </Card>

        </div>

      </div>

    </ThemeProvider>

  );

}

Animasyonlar ve Gelişmiş Özellikler

Styled Components ile karmaşık animasyonlar ve etkileşimler oluşturabilirsiniz:

import styled, { keyframes, css } from 'styled-components';


// Keyframes tanımlama

const fadeIn = keyframes`

  from {

    opacity: 0;

    transform: translateY(20px);

  }

  to {

    opacity: 1;

    transform: translateY(0);

  }

`;


const pulse = keyframes`

  0% {

    transform: scale(1);

  }

  50% {

    transform: scale(1.05);

  }

  100% {

    transform: scale(1);

  }

`;


const shimmer = keyframes`

  0% {

    background-position: -200px 0;

  }

  100% {

    background-position: calc(200px + 100%) 0;

  }

`;


// Animasyonlu bileşenler

const AnimatedCard = styled.div`

  background: white;

  border-radius: 12px;

  padding: 24px;

  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);

  animation: ${fadeIn} 0.6s ease-out;

  transition: all 0.3s ease;

  

  &:hover {

    transform: translateY(-8px);

    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);

  }

`;


const PulseButton = styled.button`

  background: linear-gradient(45deg, #ff6b6b, #ee5a24);

  color: white;

  border: none;

  padding: 12px 24px;

  border-radius: 25px;

  cursor: pointer;

  font-weight: 600;

  position: relative;

  overflow: hidden;

  

  &:hover {

    animation: ${pulse} 0.6s ease-in-out;

  }

  

  &::before {

    content: '';

    position: absolute;

    top: 0;

    left: -100%;

    width: 100%;

    height: 100%;

    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);

    transition: left 0.5s;

  }

  

  &:hover::before {

    left: 100%;

  }

`;


// Loading skeleton

const SkeletonBox = styled.div`

  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);

  background-size: 200px 100%;

  animation: ${shimmer} 2s infinite;

  border-radius: 4px;

  height: ${props => props.height || '20px'};

  width: ${props => props.width || '100%'};

  margin-bottom: ${props => props.marginBottom || '10px'};

`;


// Conditional styling

const StatusBadge = styled.span`

  padding: 4px 12px;

  border-radius: 12px;

  font-size: 0.75rem;

  font-weight: 600;

  text-transform: uppercase;

  

  ${props => {

    switch (props.status) {

      case 'success':

        return css`

          background: #d4edda;

          color: #155724;

        `;

      case 'warning':

        return css`

          background: #fff3cd;

          color: #856404;

        `;

      case 'error':

        return css`

          background: #f8d7da;

          color: #721c24;

        `;

      default:

        return css`

          background: #d1ecf1;

          color: #0c5460;

        `;

    }

  }}

`;


// Responsive grid

const Grid = styled.div`

  display: grid;

  grid-template-columns: repeat(auto-fit, minmax(${props => props.minWidth || '250px'}, 1fr));

  gap: ${props => props.gap || '20px'};

  padding: ${props => props.padding || '20px'};

  

  @media (max-width: 768px) {

    grid-template-columns: 1fr;

    gap: ${props => props.gap ? `calc(${props.gap} / 2)` : '10px'};

    padding: ${props => props.padding ? `calc(${props.padding} / 2)` : '10px'};

  }

`;


// Kullanım örneği

function LoadingCard() {

  return (

    <AnimatedCard>

      <SkeletonBox height="24px" width="60%" marginBottom="16px" />

      <SkeletonBox height="16px" width="100%" marginBottom="8px" />

      <SkeletonBox height="16px" width="80%" marginBottom="16px" />

      <SkeletonBox height="40px" width="120px" />

    </AnimatedCard>

  );

}


function ProductCard({ product, loading }) {

  if (loading) {

    return <LoadingCard />;

  }

  

  return (

    <AnimatedCard>

      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', marginBottom: '16px' }}>

        <h3>{product.name}</h3>

        <StatusBadge status={product.status}>

          {product.status}

        </StatusBadge>

      </div>

      <p style={{ color: '#666', marginBottom: '16px' }}>

        {product.description}

      </p>

      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>

        <span style={{ fontSize: '1.25rem', fontWeight: 'bold', color: '#007bff' }}>

          ${product.price}

        </span>

        <PulseButton>

          Add to Cart

        </PulseButton>

      </div>

    </AnimatedCard>

  );

}


function App() {

  const [loading, setLoading] = useState(true);

  const [products] = useState([

    { id: 1, name: 'Product 1', description: 'Great product', price: 99.99, status: 'success' },

    { id: 2, name: 'Product 2', description: 'Another product', price: 149.99, status: 'warning' },

    { id: 3, name: 'Product 3', description: 'Best product', price: 199.99, status: 'error' }

  ]);

  

  useEffect(() => {

    const timer = setTimeout(() => setLoading(false), 2000);

    return () => clearTimeout(timer);

  }, []);

  

  return (

    <div style={{ minHeight: '100vh', backgroundColor: '#f8f9fa' }}>

      <Grid minWidth="300px" gap="24px" padding="40px">

        {loading ? (

          Array(6).fill().map((_, index) => (

            <LoadingCard key={index} />

          ))

        ) : (

          products.map(product => (

            <ProductCard key={product.id} product={product} />

          ))

        )}

      </Grid>

    </div>

  );

}

Tailwind CSS

Tailwind CSS, utility-first CSS framework'üdür. Önceden tanımlanmış CSS sınıfları kullanarak hızlı ve tutarlı tasarımlar oluşturmanıza olanak tanır. React ile birlikte kullanıldığında çok güçlü bir kombinasyon oluşturur.

Kurulum ve Yapılandırma

bash npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init -p

// tailwind.config.js

module.exports = {

  content: [

    "./src/**/*.{js,jsx,ts,tsx}",

  ],

  theme: {

    extend: {

      colors: {

        primary: {

          50: '#eff6ff',

          100: '#dbeafe',

          200: '#bfdbfe',

          300: '#93c5fd',

          400: '#60a5fa',

          500: '#3b82f6',

          600: '#2563eb',

          700: '#1d4ed8',

          800: '#1e40af',

          900: '#1e3a8a',

        },

        gray: {

          50: '#f9fafb',

          100: '#f3f4f6',

          200: '#e5e7eb',

          300: '#d1d5db',

          400: '#9ca3af',

          500: '#6b7280',

          600: '#4b5563',

          700: '#374151',

          800: '#1f2937',

          900: '#111827',

        }

      },

      fontFamily: {

        sans: ['Inter', 'system-ui', 'sans-serif'],

      },

      spacing: {

        '18': '4.5rem',

        '88': '22rem',

      },

      animation: {

        'fade-in': 'fadeIn 0.5s ease-in-out',

        'slide-up': 'slideUp 0.3s ease-out',

        'bounce-in': 'bounceIn 0.6s ease-out',

      },

      keyframes: {

        fadeIn: {

          '0%': { opacity: '0' },

          '100%': { opacity: '1' },

        },

        slideUp: {

          '0%': { transform: 'translateY(10px)', opacity: '0' },

          '100%': { transform: 'translateY(0)', opacity: '1' },

        },

        bounceIn: {

          '0%': { transform: 'scale(0.3)', opacity: '0' },

          '50%': { transform: 'scale(1.05)' },

          '70%': { transform: 'scale(0.9)' },

          '100%': { transform: 'scale(1)', opacity: '1' },

        },

      },

    },

  },

  plugins: [

    require('@tailwindcss/forms'),

    require('@tailwindcss/typography'),

    require('@tailwindcss/aspect-ratio'),

  ],

}

/* src/index.css */

@tailwind base;

@tailwind components;

@tailwind utilities;


@layer components {

  .btn {

    @apply px-4 py-2 rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;

  }

  

  .btn-primary {

    @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;

  }

  

  .btn-secondary {

    @apply bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500;

  }

  

  .btn-outline {

    @apply border-2 border-primary-600 text-primary-600 hover:bg-primary-600 hover:text-white focus:ring-primary-500;

  }

  

  .card {

    @apply bg-white rounded-xl shadow-lg p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1;

  }

  

  .input {

    @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent;

  }

}

Tailwind ile Bileşen Oluşturma

// components/Button.js

import React from 'react';

import { cva } from 'class-variance-authority'; // Opsiyonel: variant yönetimi için


const buttonVariants = cva(

  'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed',

  {

    variants: {

      variant: {

        primary: 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500',

        secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500',

        outline: 'border-2 border-primary-600 text-primary-600 hover:bg-primary-600 hover:text-white focus:ring-primary-500',

        ghost: 'text-primary-600 hover:bg-primary-50 focus:ring-primary-500',

        danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',

      },

      size: {

        sm: 'px-3 py-1.5 text-sm',

        md: 'px-4 py-2 text-base',

        lg: 'px-6 py-3 text-lg',

        xl: 'px-8 py-4 text-xl',

      },

    },

    defaultVariants: {

      variant: 'primary',

      size: 'md',

    },

  }

);


function Button({ 

  children, 

  variant, 

  size, 

  loading = false, 

  className = '', 

  ...props 

}) {

  return (

    <button

      className={`${buttonVariants({ variant, size })} ${className}`}

      disabled={loading}

      {...props}

    >

      {loading && (

        <svg className="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">

          <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>

          <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>

        </svg>

      )}

      {children}

    </button>

  );

}


// components/Card.js

function Card({ title, subtitle, children, footer, className = '', ...props }) {

  return (

    <div className={`card ${className}`} {...props}>

      {(title || subtitle) && (

        <div className="mb-4">

          {title && (

            <h3 className="text-xl font-semibold text-gray-900 mb-2">

              {title}

            </h3>

          )}

          {subtitle && (

            <p className="text-gray-600 text-sm">

              {subtitle}

            </p>

          )}

        </div>

      )}

      

      {children && (

        <div className="mb-4">

          {children}

        </div>

      )}

      

      {footer && (

        <div className="pt-4 border-t border-gray-200">

          {footer}

        </div>

      )}

    </div>

  );

}


// components/Modal.js

function Modal({ isOpen, onClose, title, children, size = 'md' }) {

  if (!isOpen) return null;

  

  const sizeClasses = {

    sm: 'max-w-md',

    md: 'max-w-lg',

    lg: 'max-w-2xl',

    xl: 'max-w-4xl',

    full: 'max-w-7xl'

  };

  

  return (

    <div className="fixed inset-0 z-50 overflow-y-auto">

      <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">

        {/* Backdrop */}

        <div 

          className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75"

          onClick={onClose}

        ></div>

        

        {/* Modal */}

        <div className={`inline-block w-full ${sizeClasses[size]} p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl animate-bounce-in`}>

          {/* Header */}

          <div className="flex items-center justify-between mb-4">

            <h3 className="text-lg font-semibold text-gray-900">

              {title}

            </h3>

            <button

              onClick={onClose}

              className="p-1 text-gray-400 hover:text-gray-600 transition-colors"

            >

              <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">

                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12"></path>

              </svg>

            </button>

          </div>

          

          {/* Content */}

          <div>

            {children}

          </div>

        </div>

      </div>

    </div>

  );

}


// components/Form.js

function FormField({ label, error, children, required = false }) {

  return (

    <div className="mb-4">

      {label && (

        <label className="block text-sm font-medium text-gray-700 mb-2">

          {label}

          {required && <span className="text-red-500 ml-1">*</span>}

        </label>

      )}

      {children}

      {error && (

        <p className="mt-1 text-sm text-red-600">

          {error}

        </p>

      )}

    </div>

  );

}


function Input({ error, className = '', ...props }) {

  return (

    <input

      className={`input ${error ? 'border-red-500 focus:ring-red-500' : ''} ${className}`}

      {...props}

    />

  );

}


function TextArea({ error, className = '', rows = 4, ...props }) {

  return (

    <textarea

      rows={rows}

      className={`input resize-none ${error ? 'border-red-500 focus:ring-red-500' : ''} ${className}`}

      {...props}

    />

  );

}


// Kullanım örneği

function ContactForm() {

  const [formData, setFormData] = useState({

    name: '',

    email: '',

    message: ''

  });

  const [errors, setErrors] = useState({});

  const [loading, setLoading] = useState(false);

  const [showModal, setShowModal] = useState(false);

  

  const handleSubmit = async (e) => {

    e.preventDefault();

    setLoading(true);

    

    // Simulated API call

    await new Promise(resolve => setTimeout(resolve, 2000));

    

    setLoading(false);

    setShowModal(true);

    setFormData({ name: '', email: '', message: '' });

  };

  

  return (

    <div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">

      <div className="max-w-md mx-auto">

        <Card title="İletişim Formu" subtitle="Bizimle iletişime geçin">

          <form onSubmit={handleSubmit} className="space-y-4">

            <FormField label="İsim" required error={errors.name}>

              <Input

                type="text"

                value={formData.name}

                onChange={(e) => setFormData({...formData, name: e.target.value})}

                placeholder="Adınızı girin"

                error={errors.name}

                required

              />

            </FormField>

            

            <FormField label="E-posta" required error={errors.email}>

              <Input

                type="email"

                value={formData.email}

                onChange={(e) => setFormData({...formData, email: e.target.value})}

                placeholder="E-posta adresinizi girin"

                error={errors.email}

                required

              />

            </FormField>

            

            <FormField label="Mesaj" required error={errors.message}>

              <TextArea

                value={formData.message}

                onChange={(e) => setFormData({...formData, message: e.target.value})}

                placeholder="Mesajınızı yazın"

                error={errors.message}

                required

              />

            </FormField>

            

            <div className="flex space-x-3">

              <Button type="submit" loading={loading} className="flex-1">

                Gönder

              </Button>

              <Button 

                type="button" 

                variant="outline"

                onClick={() => setFormData({ name: '', email: '', message: '' })}

              >

                Temizle

              </Button>

            </div>

          </form>

        </Card>

      </div>

      

      <Modal

        isOpen={showModal}

        onClose={() => setShowModal(false)}

        title="Başarılı!"

      >

        <div className="text-center">

          <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4">

            <svg className="h-6 w-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">

              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>

            </svg>

          </div>

          <p className="text-gray-600 mb-6">

            Mesajınız başarıyla gönderildi. En kısa sürede size dönüş yapacağız.

          </p>

          <Button onClick={() => setShowModal(false)}>

            Tamam

          </Button>

        </div>

      </Modal>

    </div>

  );

}

React'te stil yönetimi için birçok yaklaşım bulunur. CSS Modülleri basit ve etkili bir çözüm sunarken, Styled Components daha dinamik ve JavaScript entegreli bir yaklaşım sağlar. Tailwind CSS ise hızlı prototipleme ve tutarlı tasarım sistemi oluşturma konusunda çok güçlüdür. Hangi yaklaşımı seçeceğiniz, projenizin gereksinimlerine, takım tercihlerine ve performans ihtiyaçlarına bağlıdır.