Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Styled Components - Kosmiczna fuzja JSX i CSS

W naszej kosmicznej podróży przez świat stylowania w React, docieramy do fascynującej technologii, która zaciera granice między kodem a stylem. Styled Components to jak zaawansowany statek międzygwiezdny, który łączy w sobie DNA dwóch światów - JavaScriptu i CSS - tworząc nową formę życia, idealnie dostosowaną do ekosystemu React.

Czym są Styled Components?

Styled Components to jedna z najpopularniejszych bibliotek CSS-in-JS, która pozwala na pisanie faktycznego CSS bezpośrednio w plikach JavaScript/JSX, jednocześnie tworząc normalne komponenty React. To rewolucyjne podejście do stylowania pozwala nam myśleć o naszych komponentach jako w pełni zamkniętych, autonomicznych jednostkach, gdzie zarówno logika, jak i styl są zebrane w jednym miejscu.

Wyobraź sobie komponenty jak moduły statku kosmicznego - każdy zawiera zarówno swoją funkcję (silniki, systemy podtrzymywania życia, nawigacja), jak i swój wygląd (panele sterowania, wskaźniki, interfejsy) - wszystko hermetycznie zamknięte w jednej, spójnej całości.

Instalacja i konfiguracja

Zanim rozpoczniemy naszą przygodę ze Styled Components, musimy zainstalować bibliotekę:

1npm install styled-components
2# lub
3yarn add styled-components
4# lub
5pnpm add styled-components

Dla użytkowników TypeScript, warto dodać również typy:

1npm install @types/styled-components

Podstawy Styled Components

Tworzenie prostego komponentu stylizowanego

Tworzenie stylizowanych komponentów jest niezwykle intuicyjne. Wystarczy użyć funkcji

styled
zaimportowanej z biblioteki, a następnie nazwę tagu HTML, do którego chcemy aplikować style:

1import styled from 'styled-components';
2
3// Tworzenie stylizowanego przycisku
4const Button = styled.button`
5  background-color: #4a90e2;
6  color: white;
7  padding: 10px 15px;
8  border: none;
9  border-radius: 4px;
10  font-weight: bold;
11  cursor: pointer;
12  transition: all 0.3s ease;
13  
14  &:hover {
15    background-color: #357ae8;
16    transform: translateY(-2px);
17    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
18  }
19  
20  &:active {
21    transform: translateY(1px);
22  }
23`;
24
25// Użycie komponentu
26function App() {
27  return (
28    <div>
29      <h1>Centrum Dowodzenia</h1>
30      <Button>Uruchom procedurę startową</Button>
31    </div>
32  );
33}

W powyższym przykładzie stworzyliśmy stylizowany komponent

Button
, który działa jak normalny przycisk HTML, ale ma predefiniowane style. Zauważ charakterystyczną składnię z użyciem backticks (``), która pozwala na pisanie wieloliniowego CSS. Co więcej, możemy używać selektora
&
jak w Sass, aby odwoływać się do samego komponentu.

Dziedziczenie stylów

Możemy rozszerzać istniejące komponenty stylizowane, dodając do nich nowe style:

1// Bazowy przycisk
2const Button = styled.button`
3  padding: 10px 15px;
4  border: none;
5  border-radius: 4px;
6  font-weight: bold;
7  cursor: pointer;
8  transition: all 0.3s ease;
9`;
10
11// Rozszerzenie bazowego przycisku
12const PrimaryButton = styled(Button)`
13  background-color: #4a90e2;
14  color: white;
15  
16  &:hover {
17    background-color: #357ae8;
18  }
19`;
20
21// Kolejne rozszerzenie
22const DangerButton = styled(Button)`
23  background-color: #e74c3c;
24  color: white;
25  
26  &:hover {
27    background-color: #c0392b;
28  }
29`;

Praca z props (parametrami)

Jedną z najpotężniejszych funkcji Styled Components jest możliwość dynamicznego stylowania w oparciu o propsy przekazywane do komponentu:

1const Button = styled.button`
2  padding: 10px 15px;
3  border: none;
4  border-radius: 4px;
5  font-weight: bold;
6  cursor: pointer;
7  transition: all 0.3s ease;
8  
9  /* Dynamiczne kolory na podstawie props */
10  background-color: props => props.primary ? '#4a90e2' : props.danger ? '#e74c3c' : props.success ? '#2ecc71' : '#95a5a6';
11  color: white;
12  
13  /* Dynamiczny rozmiar */
14  font-size: props => (props.large ? '18px' : '14px');
15  padding: props => (props.large ? '12px 20px' : '8px 12px');
16  
17  /* Zablokowany przycisk */
18  opacity: props => (props.disabled ? 0.5 : 1);
19  cursor: props => (props.disabled ? 'not-allowed' : 'pointer');
20  
21  &:hover {
22    ${props => !props.disabled && `
23      transform: translateY(-2px);
24      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
25    `}
26  }
27`;
28
29// Użycie z różnymi propsami
30function ActionPanel() {
31  return (
32    <div>
33      <Button>Normalny</Button>
34      <Button primary>Podstawowy</Button>
35      <Button danger>Usuń</Button>
36      <Button success large>Zapisz</Button>
37      <Button disabled>Niedostępny</Button>
38    </div>
39  );
40}

W przykładzie powyżej, styl przycisku zmienia się w zależności od przekazanych propsów. Możemy przekazywać dowolne propsy do naszych stylizowanych komponentów, a następnie używać ich w wyrażeniach wewnątrz literałów szablonowych.

Prop-forwarding i atrybuty HTML

Styled Components automatycznie przekazują wszystkie nieużyte propsy do elementu DOM:

1// Ten przycisk będzie miał typ "submit" i obsługę onClick
2function FormSubmit() {
3  return (
4    <Button 
5      type="submit" 
6      onClick={() => console.log('Formularz wysłany!')}
7    >
8      Wyślij
9    </Button>
10  );
11}

Stylizacja istniejących komponentów

Możemy również stylizować istniejące komponenty React, o ile przyjmują one prop

className
:

1// Zwykły komponent React
2function CustomInput({ className, ...props }) {
3  return (
4    <input 
5      className={className} 
6      {...props} 
7    />
8  );
9}
10
11// Stylizacja istniejącego komponentu
12const StyledCustomInput = styled(CustomInput)`
13  border: 2px solid #3498db;
14  border-radius: 4px;
15  padding: 8px 12px;
16  width: 100%;
17  font-size: 16px;
18  
19  &:focus {
20    outline: none;
21    border-color: #2980b9;
22    box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.3);
23  }
24`;

Zaawansowane funkcje Styled Components

Globalne style

Styled Components oferuje również komponent

GlobalStyle
do definiowania globalnych stylów:

1import { createGlobalStyle } from 'styled-components';
2
3const GlobalStyle = createGlobalStyle`
4  /* Reset CSS */
5  *, *::before, *::after {
6    box-sizing: border-box;
7    margin: 0;
8    padding: 0;
9  }
10  
11  /* Zmienne globalne */
12  :root {
13    --primary-color: #4a90e2;
14    --secondary-color: #8a2be2;
15    --success-color: #2ecc71;
16    --danger-color: #e74c3c;
17    --warning-color: #f1c40f;
18    --text-light: #ecf0f1;
19    --text-dark: #2c3e50;
20    --background-dark: #1a1a2e;
21    --background-light: #f5f5f5;
22  }
23  
24  body {
25    font-family: 'Space Grotesk', sans-serif;
26    line-height: 1.6;
27    background-color: var(--background-dark);
28    color: var(--text-light);
29  }
30  
31  a {
32    color: var(--primary-color);
33    text-decoration: none;
34  }
35  
36  button {
37    font-family: inherit;
38  }
39`;
40
41// Użycie GlobalStyle w aplikacji
42function App() {
43  return (
44    <>
45      <GlobalStyle />
46      <div>
47        {/* Zawartość aplikacji */}
48      </div>
49    </>
50  );
51}

Theming (tematyzacja)

Jedną z największych zalet Styled Components jest wsparcie dla tematów:

1import { ThemeProvider } from 'styled-components';
2
3// Definicja motywu
4const darkTheme = {
5  colors: {
6    primary: '#4a90e2',
7    secondary: '#8a2be2',
8    success: '#2ecc71',
9    danger: '#e74c3c',
10    warning: '#f1c40f',
11    background: '#1a1a2e',
12    text: '#ecf0f1',
13    muted: '#95a5a6'
14  },
15  fonts: {
16    body: "'Space Grotesk', sans-serif",
17    heading: "'Nova Mono', monospace"
18  },
19  spacing: {
20    xs: '4px',
21    sm: '8px',
22    md: '16px',
23    lg: '24px',
24    xl: '32px'
25  },
26  borderRadius: {
27    small: '4px',
28    medium: '8px',
29    large: '12px',
30    round: '50%'
31  },
32  shadows: {
33    small: '0 2px 4px rgba(0, 0, 0, 0.1)',
34    medium: '0 4px 8px rgba(0, 0, 0, 0.2)',
35    large: '0 8px 16px rgba(0, 0, 0, 0.3)'
36  }
37};
38
39// Alternatywny motyw
40const lightTheme = {
41  colors: {
42    primary: '#3498db',
43    secondary: '#9b59b6',
44    success: '#27ae60',
45    danger: '#c0392b',
46    warning: '#f39c12',
47    background: '#f5f5f5',
48    text: '#2c3e50',
49    muted: '#7f8c8d'
50  },
51  // Pozostałe właściwości takie same jak w darkTheme
52  fonts: darkTheme.fonts,
53  spacing: darkTheme.spacing,
54  borderRadius: darkTheme.borderRadius,
55  shadows: darkTheme.shadows
56};
57
58// Stylizowany komponent używający motywu
59const Card = styled.div`
60  background-color: props => props.theme.colors.background === '#1a1a2e' ? '#252941' : '#ffffff';
61  color: props => props.theme.colors.text;
62  border-radius: props => props.theme.borderRadius.medium;
63  padding: props => props.theme.spacing.md;
64  box-shadow: props => props.theme.shadows.small;
65  
66  transition: all 0.3s ease;
67  
68  &:hover {
69    box-shadow: props => props.theme.shadows.medium;
70  }
71`;
72
73// Stylizowany przycisk używający motywu
74const Button = styled.button`
75  background-color: ${props => props.theme.colors.primary};
76  color: white;
77  padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
78  border: none;
79  border-radius: ${props => props.theme.borderRadius.small};
80  font-family: ${props => props.theme.fonts.body};
81  cursor: pointer;
82  
83  &:hover {
84    background-color: ${props => {
85      const color = props.theme.colors.primary;
86      // Prostą funkcję ciemniejszego koloru
87      return color.replace(/rgb((d+), (d+), (d+))/, (_, r, g, b) => 
88        `rgb(${Math.max(0, r - 20)}, ${Math.max(0, g - 20)}, ${Math.max(0, b - 20)})`
89      );
90    }};
91  }
92`;
93
94// Użycie ThemeProvider i komponentów tematycznych
95function App() {
96  const [darkMode, setDarkMode] = useState(true);
97  const theme = darkMode ? darkTheme : lightTheme;
98  
99  return (
100    <ThemeProvider theme={theme}>
101      <GlobalStyle />
102      <AppContainer>
103        <Header>
104          <h1>Kosmiczne Centrum Dowodzenia</h1>
105          <Button onClick={() => setDarkMode(!darkMode)}>
106            Przełącz tryb {darkMode ? "jasny" : "ciemny"}
107          </Button>
108        </Header>
109        
110        <Card>
111          <h2>Panel Kontrolny</h2>
112          <p>Tutaj możesz zarządzać swoją kosmiczną misją.</p>
113          <Button>Uruchom</Button>
114        </Card>
115      </AppContainer>
116    </ThemeProvider>
117  );
118}

Animacje

Styled Components pozwala na łatwe tworzenie animacji z użyciem helper funkcji

keyframes
:

1import styled, { keyframes } from 'styled-components';
2
3// Definicja animacji
4const pulse = keyframes`
5  0% {
6    opacity: 1;
7    transform: scale(1);
8  }
9  50% {
10    opacity: 0.8;
11    transform: scale(1.05);
12  }
13  100% {
14    opacity: 1;
15    transform: scale(1);
16  }
17`;
18
19const rotate = keyframes`
20  from {
21    transform: rotate(0deg);
22  }
23  to {
24    transform: rotate(360deg);
25  }
26`;
27
28// Użycie animacji w komponencie
29const PulsingButton = styled.button`
30  background-color: #e74c3c;
31  color: white;
32  padding: 12px 24px;
33  border: none;
34  border-radius: 4px;
35  font-weight: bold;
36  
37  /* Aplikacja animacji */
38  animation: ${pulse} 2s infinite ease-in-out;
39`;
40
41const Spinner = styled.div`
42  width: 40px;
43  height: 40px;
44  border: 4px solid rgba(255, 255, 255, 0.3);
45  border-top: 4px solid #3498db;
46  border-radius: 50%;
47  
48  /* Animacja rotacji */
49  animation: ${rotate} 1s linear infinite;
50`;
51
52// Animacje warunkowe
53const AlertBadge = styled.span`
54  display: inline-block;
55  padding: 4px 8px;
56  background-color: red;
57  color: white;
58  border-radius: 10px;
59  font-size: 12px;
60  
61  /* Animacja tylko gdy jest ważny alert */
62  animation: ${props => props.critical ? 
63    css`${pulse} 1s infinite` : 'none'};
64`;

Pseudoelementy i pseudoklasy

Styled Components obsługują wszystkie pseudoelementy i pseudoklasy CSS:

1const HoverCard = styled.div`
2  position: relative;
3  padding: 20px;
4  background-color: #252941;
5  border-radius: 8px;
6  transition: all 0.3s ease;
7  
8  /* Pseudoklasy */
9  &:hover {
10    transform: translateY(-5px);
11    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
12  }
13  
14  &:active {
15    transform: translateY(-2px);
16  }
17  
18  /* Pseudoelementy */
19  &::before {
20    content: "";
21    position: absolute;
22    top: -10px;
23    left: -10px;
24    right: -10px;
25    bottom: -10px;
26    border-radius: 12px;
27    background: linear-gradient(45deg, #4a90e2, #8a2be2);
28    z-index: -1;
29    opacity: 0;
30    transition: opacity 0.3s ease;
31  }
32  
33  &:hover::before {
34    opacity: 1;
35  }
36  
37  /* Zagnieżdżone selektory */
38  h3 {
39    margin-bottom: 10px;
40    color: #ecf0f1;
41  }
42  
43  p {
44    color: #bdc3c7;
45  }
46  
47  /* Złożone selektory */
48  &:hover h3 {
49    color: #4a90e2;
50  }
51`;

Media queries

Styled Components doskonale obsługuje media queries, umożliwiając tworzenie responsywnych komponentów:

1const ResponsiveContainer = styled.div`
2  padding: 20px;
3  background-color: #252941;
4  border-radius: 8px;
5  
6  /* Bazowa szerokość */
7  width: 100%;
8  
9  /* Media queries */
10  @media (min-width: 768px) {
11    width: 750px;
12    margin: 0 auto;
13  }
14  
15  @media (min-width: 992px) {
16    width: 970px;
17  }
18  
19  @media (min-width: 1200px) {
20    width: 1170px;
21  }
22  
23  /* Responsywne zmiany stylów */
24  h1 {
25    font-size: 24px;
26    
27    @media (min-width: 768px) {
28      font-size: 32px;
29    }
30  }
31  
32  /* Możemy używać media queries wewnątrz warunków */
33  display: ${props => props.hidden ?
34    `
35      none;
36      @media (min-width: 768px) {
37        display: block;
38      }
39    ` : 'block'
40  };
41`;

Helpers i pomocnicze funkcje

Styled Components dostarcza kilka użytecznych narzędzi, jak

css
do wielokrotnego używania fragmentów stylu:

1import styled, { css } from 'styled-components';
2
3// Tworzenie reużywalnych fragmentów stylów
4const flexCenter = css`
5  display: flex;
6  justify-content: center;
7  align-items: center;
8`;
9
10const buttonBase = css`
11  padding: 10px 15px;
12  border: none;
13  border-radius: 4px;
14  font-weight: bold;
15  cursor: pointer;
16  transition: all 0.3s ease;
17`;
18
19const dangerStyles = css`
20  background-color: #e74c3c;
21  color: white;
22  
23  &:hover {
24    background-color: #c0392b;
25  }
26`;
27
28// Użycie fragmentów w komponentach
29const FlexContainer = styled.div`
30  ${flexCenter}
31  gap: 20px;
32  flex-wrap: wrap;
33`;
34
35const Button = styled.button`
36  ${buttonBase}
37  
38  background-color: #4a90e2;
39  color: white;
40  
41  &:hover {
42    background-color: #357ae8;
43  }
44  
45  ${props => props.danger && dangerStyles}
46`;

Zaawansowane wzorce i techniki

Reużywalne komponenty i ich wariacje

Jedną z najlepszych praktyk w Styled Components jest tworzenie reużywalnych komponentów bazowych z wariacjami:

1// Bazowy komponent przycisku
2const BaseButton = styled.button`
3  padding: 10px 15px;
4  border: none;
5  border-radius: 4px;
6  font-weight: bold;
7  cursor: pointer;
8  transition: all 0.3s ease;
9  font-size: 14px;
10`;
11
12// Warianty przycisków
13const PrimaryButton = styled(BaseButton)`
14  background-color: ${props => props.theme.colors.primary};
15  color: white;
16  
17  &:hover {
18    background-color: ${props => darken(0.1, props.theme.colors.primary)};
19  }
20`;
21
22const SecondaryButton = styled(BaseButton)`
23  background-color: transparent;
24  color: ${props => props.theme.colors.primary};
25  border: 2px solid ${props => props.theme.colors.primary};
26  
27  &:hover {
28    background-color: ${props => rgba(props.theme.colors.primary, 0.1)};
29  }
30`;
31
32const DangerButton = styled(BaseButton)`
33  background-color: ${props => props.theme.colors.danger};
34  color: white;
35  
36  &:hover {
37    background-color: ${props => darken(0.1, props.theme.colors.danger)};
38  }
39`;
40
41// Rozmiary przycisków
42const SmallButton = css`
43  padding: 6px 10px;
44  font-size: 12px;
45`;
46
47const LargeButton = css`
48  padding: 12px 20px;
49  font-size: 16px;
50`;
51
52// Komponent wyższego rzędu dla rozmiaru
53const sizedButton = (Component) => styled(Component)`
54  ${props => props.small && SmallButton}
55  ${props => props.large && LargeButton}
56`;
57
58// Tworzenie wariantów z rozmiarami
59const SizedPrimaryButton = sizedButton(PrimaryButton);
60const SizedSecondaryButton = sizedButton(SecondaryButton);
61const SizedDangerButton = sizedButton(DangerButton);
62
63// Użycie
64function ActionPanel() {
65  return (
66    <div>
67      <SizedPrimaryButton>Normalne</SizedPrimaryButton>
68      <SizedPrimaryButton small>Małe</SizedPrimaryButton>
69      <SizedPrimaryButton large>Duże</SizedPrimaryButton>
70      
71      <SizedSecondaryButton>Normalne</SizedSecondaryButton>
72      <SizedDangerButton large>Duży alarm</SizedDangerButton>
73    </div>
74  );
75}

Pomysłowe wykorzystanie atrybutu
as

Styled Components pozwala na zmianę elementu HTML używanego przez komponent za pomocą propsu

as
:

1const Button = styled.button`
2  background-color: #4a90e2;
3  color: white;
4  padding: 10px 15px;
5  border: none;
6  border-radius: 4px;
7  font-weight: bold;
8  cursor: pointer;
9  display: inline-flex;
10  align-items: center;
11  justify-content: center;
12  text-decoration: none;
13`;
14
15function App() {
16  return (
17    <div>
18      <Button>Normalny przycisk</Button>
19      
20      {/* Renderowanie jako link */}
21      <Button as="a" href="/destination">
22        Przycisk jako link
23      </Button>
24      
25      {/* Renderowanie jako inny element */}
26      <Button as="div" role="button" tabIndex={0}>
27        Przycisk jako div
28      </Button>
29      
30      {/* Możemy użyć innego komponentu React */}
31      <Button as={Link} to="/dashboard">
32        Przycisk jako router Link
33      </Button>
34    </div>
35  );
36}

Warunkowa stylizacja

Styled Components pozwala na bardzo szczegółową warunkową stylizację:

1const AlertBox = styled.div`
2  padding: 15px;
3  border-radius: 8px;
4  margin-bottom: 20px;
5  display: flex;
6  align-items: center;
7  
8  /* Zmiana koloru tła na podstawie typu alertu */
9  background-color: ${props =>
10    props.type === 'success' ? '#d4edda' :
11    props.type === 'danger' ? '#f8d7da' :
12    props.type === 'warning' ? '#fff3cd' :
13    props.type === 'info' ? '#d1ecf1' :
14    '#e2e3e5'
15  };
16  
17  /* Zmiana koloru tekstu */
18  color: ${props =>
19    props.type === 'success' ? '#155724' :
20    props.type === 'danger' ? '#721c24' :
21    props.type === 'warning' ? '#856404' :
22    props.type === 'info' ? '#0c5460' :
23    '#383d41'
24  };
25  
26  /* Zmiana obramowania */
27  border: 1px solid ${props =>
28    props.type === 'success' ? '#c3e6cb' :
29    props.type === 'danger' ? '#f5c6cb' :
30    props.type === 'warning' ? '#ffeeba' :
31    props.type === 'info' ? '#bee5eb' :
32    '#d6d8db'
33  };
34  
35  /* Zmiana ikon */
36  &::before {
37    content: "${props =>
38      props.type === 'success' ? '✅' :
39      props.type === 'danger' ? '❌' :
40      props.type === 'warning' ? '⚠️' :
41      props.type === 'info' ? 'ℹ️' :
42      'ℹ️'
43    }";
44    margin-right: 10px;
45    font-size: 18px;
46  }
47  
48  /* Możemy również używać zagnieżdżonych warunków i funkcji */
49  ${props => {
50    if (props.dismissable) {
51      return `
52        padding-right: 40px;
53        position: relative;
54      `;
55    }
56  }}
57`;
58
59// Przycisk zamknięcia dla odrzucalnych alertów
60const CloseButton = styled.button`
61  position: absolute;
62  top: 10px;
63  right: 10px;
64  background: none;
65  border: none;
66  font-size: 20px;
67  cursor: pointer;
68  color: inherit;
69  opacity: 0.5;
70  
71  &:hover {
72    opacity: 1;
73  }
74`;
75
76// Użycie komponentu
77function Notifications() {
78  return (
79    <div>
80      <AlertBox type="success">
81        Misja zakończona powodzeniem!
82      </AlertBox>
83      
84      <AlertBox type="danger" dismissable>
85        Uwaga: Wykryto awarię systemu!
86        <CloseButton>&times;</CloseButton>
87      </AlertBox>
88      
89      <AlertBox type="warning">
90        Ostrzeżenie: Niski poziom paliwa.
91      </AlertBox>
92      
93      <AlertBox type="info">
94        Informacja: Zbliżasz się do stacji kosmicznej.
95      </AlertBox>
96    </div>
97  );
98}

Praktyczny przykład: Kosmiczny Dashboard

Zobaczmy, jak można stworzyć bardziej złożony interfejs używając Styled Components:

1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle, keyframes } from 'styled-components';
3
4// ========== Theme Definition ==========
5const theme = {
6  colors: {
7    primary: '#4a90e2',
8    secondary: '#8a2be2',
9    success: '#2ecc71',
10    danger: '#e74c3c',
11    warning: '#f1c40f',
12    info: '#3498db',
13    background: '#0f1429',
14    cardBg: '#1a1f3c',
15    text: '#ecf0f1',
16    textMuted: '#95a5a6',
17    border: '#2c3e50'
18  },
19  spacing: {
20    xs: '4px',
21    sm: '8px',
22    md: '16px',
23    lg: '24px',
24    xl: '32px'
25  },
26  fonts: {
27    body: "'Space Grotesk', sans-serif",
28    mono: "'JetBrains Mono', monospace"
29  },
30  borderRadius: {
31    sm: '4px',
32    md: '8px',
33    lg: '16px'
34  }
35};
36
37// ========== Global Style ==========
38const GlobalStyle = createGlobalStyle`
39  * {
40    box-sizing: border-box;
41    margin: 0;
42    padding: 0;
43  }
44  
45  body {
46    font-family: ${props => props.theme.fonts.body};
47    background-color: ${props => props.theme.colors.background};
48    color: ${props => props.theme.colors.text};
49    line-height: 1.6;
50  }
51  
52  h1, h2, h3, h4, h5, h6 {
53    margin-bottom: ${props => props.theme.spacing.md};
54  }
55`;
56
57// ========== Animations ==========
58const fadeIn = keyframes`
59  from {
60    opacity: 0;
61    transform: translateY(10px);
62  }
63  to {
64    opacity: 1;
65    transform: translateY(0);
66  }
67`;
68
69const pulse = keyframes`
70  0% {
71    transform: scale(1);
72  }
73  50% {
74    transform: scale(1.05);
75  }
76  100% {
77    transform: scale(1);
78  }
79`;
80
81const rotate = keyframes`
82  from {
83    transform: rotate(0deg);
84  }
85  to {
86    transform: rotate(360deg);
87  }
88`;
89
90// ========== Layout Components ==========
91const AppContainer = styled.div`
92  max-width: 1200px;
93  margin: 0 auto;
94  padding: ${props => props.theme.spacing.lg};
95`;
96
97const Header = styled.header`
98  display: flex;
99  justify-content: space-between;
100  align-items: center;
101  margin-bottom: ${props => props.theme.spacing.xl};
102  padding-bottom: ${props => props.theme.spacing.md};
103  border-bottom: 1px solid ${props => props.theme.colors.border};
104  
105  animation: ${fadeIn} 0.5s ease-out forwards;
106`;
107
108const MainTitle = styled.h1`
109  color: ${props => props.theme.colors.primary};
110  font-size: 28px;
111  display: flex;
112  align-items: center;
113  
114  &::before {
115    content: "🚀";
116    margin-right: ${props => props.theme.spacing.sm};
117  }
118`;
119
120const Grid = styled.div`
121  display: grid;
122  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
123  gap: ${props => props.theme.spacing.lg};
124  margin-bottom: ${props => props.theme.spacing.xl};
125  
126  animation: ${fadeIn} 0.5s ease-out forwards;
127  animation-delay: 0.2s;
128  opacity: 0;
129`;
130
131const WideSection = styled.section`
132  grid-column: 1 / -1;
133  animation: ${fadeIn} 0.5s ease-out forwards;
134  animation-delay: 0.4s;
135  opacity: 0;
136`;
137
138// ========== Card Components ==========
139const Card = styled.div`
140  background-color: ${props => props.theme.colors.cardBg};
141  border-radius: ${props => props.theme.borderRadius.md};
142  padding: ${props => props.theme.spacing.lg};
143  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
144  
145  ${props => props.interactive && `
146    cursor: pointer;
147    transition: all 0.3s ease;
148    
149    &:hover {
150      transform: translateY(-5px);
151      box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
152    }
153  `}
154  
155  ${props => props.primary && `
156    border-top: 4px solid ${props.theme.colors.primary};
157  `}
158  
159  ${props => props.warning && `
160    border-top: 4px solid ${props.theme.colors.warning};
161  `}
162  
163  ${props => props.danger && `
164    border-top: 4px solid ${props.theme.colors.danger};
165  `}
166  
167  ${props => props.success && `
168    border-top: 4px solid ${props.theme.colors.success};
169  `}
170`;
171
172const CardTitle = styled.h2`
173  font-size: 18px;
174  display: flex;
175  align-items: center;
176  margin-bottom: ${props => props.theme.spacing.md};
177  
178  ${props => props.icon && `
179    &::before {
180      content: "${props.icon}";
181      margin-right: ${props.theme.spacing.sm};
182    }
183  `}
184`;
185
186const CardContent = styled.div`
187  margin-bottom: ${props => props.theme.spacing.md};
188`;
189
190// ========== Button Components ==========
191const Button = styled.button`
192  background-color: ${props => 
193    props.primary ? props.theme.colors.primary :
194    props.danger ? props.theme.colors.danger :
195    props.success ? props.theme.colors.success :
196    props.warning ? props.theme.colors.warning :
197    '#95a5a6'
198  };
199  color: white;
200  border: none;
201  border-radius: ${props => props.theme.borderRadius.sm};
202  padding: ${props => props.small ? '6px 12px' : '10px 20px'};
203  font-family: ${props => props.theme.fonts.body};
204  font-size: ${props => props.small ? '12px' : '14px'};
205  font-weight: bold;
206  cursor: pointer;
207  transition: all 0.3s ease;
208  
209  &:hover {
210    filter: brightness(1.1);
211    transform: translateY(-2px);
212    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
213  }
214  
215  &:active {
216    transform: translateY(0);
217  }
218  
219  ${props => props.ghost && `
220    background-color: transparent;
221    color: ${
222      props.primary ? props.theme.colors.primary :
223      props.danger ? props.theme.colors.danger :
224      props.success ? props.theme.colors.success :
225      props.warning ? props.theme.colors.warning :
226      props.theme.colors.text
227    };
228    border: 1px solid currentColor;
229    
230    &:hover {
231      background-color: rgba(255, 255, 255, 0.05);
232    }
233  `}
234  
235  ${props => props.icon && `
236    display: inline-flex;
237    align-items: center;
238    
239    &::before {
240      content: "${props.icon}";
241      margin-right: 8px;
242    }
243  `}
244  
245  ${props => props.block && `
246    display: block;
247    width: 100%;
248  `}
249  
250  ${props => props.disabled && `
251    opacity: 0.5;
252    cursor: not-allowed;
253    pointer-events: none;
254  `}
255  
256  & + & {
257    margin-left: props => props.theme.spacing.sm;
258  }
259`;
260
261// ========== Data Display Components ==========
262const Statistic = styled.div`
263  text-align: center;
264`;
265
266const StatValue = styled.div`
267  font-size: 36px;
268  font-weight: bold;
269  color: props => props.primary
270    ? props.theme.colors.primary
271    : props.danger
272        ? props.theme.colors.danger
273        : props.success
274            ? props.theme.colors.success
275            : props.warning
276                ? props.theme.colors.warning
277                : props.theme.colors.text;
278  margin-bottom: props => props.theme.spacing.xs;
279  
280  ${props => props.pulse && `
281    animation: ${pulse} 2s infinite ease-in-out;
282  `}
283`;
284
285const StatLabel = styled.div`
286  font-size: 14px;
287  color: props => props.theme.colors.textMuted;
288  text-transform: uppercase;
289`;
290
291const ProgressBar = styled.div`
292  height: 8px;
293  background-color: rgba(0, 0, 0, 0.2);
294  border-radius: 4px;
295  overflow: hidden;
296  margin-bottom: props => props.theme.spacing.md;
297  
298  &::after {
299    content: '';
300    display: block;
301    height: 100%;
302    width: ${props => `${props.value}%`};
303    background-color: props => props.primary
304    ? props.theme.colors.primary
305    : props.danger
306        ? props.theme.colors.danger
307        : props.success
308            ? props.theme.colors.success
309            : props.warning
310                ? props.theme.colors.warning
311                : props.theme.colors.primary;
312    transition: width 0.5s ease-in-out;
313  }
314`;
315
316const StatusIndicator = styled.span`
317  display: inline-block;
318  width: 12px;
319  height: 12px;
320  border-radius: 50%;
321  margin-right: props => props.theme.spacing.sm;
322  background-color: props => props.status === 'online'
323    ? props.theme.colors.success
324    : props.status === 'warning'
325        ? props.theme.colors.warning
326        : props.status === 'critical'
327            ? props.theme.colors.danger
328            : props.status === 'offline'
329                ? '#95a5a6'
330                : props.theme.colors.info;
331  
332  ${props => props.status === 'online' && `
333    box-shadow: 0 0 10px ${props.theme.colors.success};
334  `}
335  
336 ${props => props.status === 'critical' && `
337    animation: ${pulse} 1s infinite;
338  `}
339`;
340
341// ========== Table Components ==========
342const Table = styled.table`
343  width: 100%;
344  border-collapse: collapse;
345`;
346
347const TableHeader = styled.th`
348  text-align: left;
349  padding: ${props => props.theme.spacing.sm};
350  border-bottom: 2px solid ${props => props.theme.colors.border};
351  color: ${props => props.theme.colors.textMuted};
352  font-weight: 500;
353`;
354
355const TableCell = styled.td`
356  padding: ${props => props.theme.spacing.sm};
357  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
358`;
359
360const TableRow = styled.tr`
361  transition: background-color 0.2s ease;
362  
363  &:hover {
364    background-color: rgba(255, 255, 255, 0.05);
365  }
366`;
367
368// ========== Loading Spinner ==========
369const Spinner = styled.div`
370  display: inline-block;
371  width: 24px;
372  height: 24px;
373  border: 3px solid rgba(255, 255, 255, 0.3);
374  border-radius: 50%;
375  border-top-color: ${props => props.theme.colors.primary};
376  animation: ${rotate} 1s linear infinite;
377  margin-right: ${props => props.theme.spacing.sm};
378`;
379
380// ========== Application Component ==========
381function SpaceDashboard() {
382  const [systemStatus, setSystemStatus] = useState('online');
383  const [fuelLevel, setFuelLevel] = useState(72);
384  const [oxygenLevel, setOxygenLevel] = useState(93);
385  const [alerts, setAlerts] = useState([
386    { id: 1, message: "Zmiana ciśnienia w module C", severity: "warning", time: "10:23" },
387    { id: 2, message: "Aktualizacja nawigacji zainstalowana", severity: "info", time: "09:15" },
388    { id: 3, message: "Cząsteczki mikrometeoroidów wykryte", severity: "warning", time: "07:42" },
389  ]);
390  
391  const crewMembers = [
392    { id: 1, name: "Alex Chen", role: "Kapitan", status: "Mostek", avatar: "👨‍🚀" },
393    { id: 2, name: "Maria Rodriguez", role: "Inżynier", status: "Maszynownia", avatar: "👩‍🚀" },
394    { id: 3, name: "Jackson Lee", role: "Nawigator", status: "Mostek", avatar: "👨‍🚀" },
395    { id: 4, name: "Sophia Kim", role: "Lekarz", status: "Laboratorium", avatar: "👩‍🚀" },
396  ];
397  
398  const toggleSystemStatus = () => {
399    setSystemStatus(systemStatus === 'online' ? 'warning' : 'online');
400  };
401  
402  return (
403    <ThemeProvider theme={theme}>
404      <GlobalStyle />
405      <AppContainer>
406        <Header>
407          <MainTitle>Kosmiczne Centrum Dowodzenia</MainTitle>
408          <div>
409            <Button primary icon="🚀" onClick={toggleSystemStatus}>
410              System {systemStatus === 'online' ? 'Online' : 'Uwaga'}
411            </Button>
412          </div>
413        </Header>
414        
415        <Grid>
416          {/* System Status Card */}
417          <Card primary>
418            <CardTitle icon="🛰️">Status Systemów</CardTitle>
419            <CardContent>
420              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '20px' }}>
421                <div>
422                  <StatusIndicator status={systemStatus} />
423                  Status główny: {
424                    systemStatus === 'online' ? 'Wszystkie systemy sprawne' :
425                    systemStatus === 'warning' ? 'Uwaga - sprawdź alerty' :
426                    'Usterka'
427                  }
428                </div>
429              </div>
430              
431              <div>
432                <div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between' }}>
433                  <span>Poziom paliwa</span>
434                  <span>{fuelLevel}%</span>
435                </div>
436                <ProgressBar 
437                  value={fuelLevel} 
438                  warning={fuelLevel < 50} 
439                  danger={fuelLevel < 20}
440                  success={fuelLevel >= 50}
441                />
442                
443                <div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between' }}>
444                  <span>Poziom tlenu</span>
445                  <span>{oxygenLevel}%</span>
446                </div>
447                <ProgressBar 
448                  value={oxygenLevel} 
449                  warning={oxygenLevel < 70} 
450                  danger={oxygenLevel < 40}
451                  success={oxygenLevel >= 70}
452                />
453              </div>
454            </CardContent>
455            <Button ghost primary block>Szczegółowe statystyki</Button>
456          </Card>
457          
458          {/* Crew Status Card */}
459          <Card success>
460            <CardTitle icon="👩‍🚀">Status Załogi</CardTitle>
461            <CardContent>
462              <Table>
463                <thead>
464                  <tr>
465                    <TableHeader></TableHeader>
466                    <TableHeader>Imię</TableHeader>
467                    <TableHeader>Lokalizacja</TableHeader>
468                  </tr>
469                </thead>
470                <tbody>
471                  {crewMembers.map(member => (
472                    <TableRow key={member.id}>
473                      <TableCell>{member.avatar}</TableCell>
474                      <TableCell>
475                        {member.name}
476                        <div style={{ fontSize: '12px', color: theme.colors.textMuted }}>
477                          {member.role}
478                        </div>
479                      </TableCell>
480                      <TableCell>{member.status}</TableCell>
481                    </TableRow>
482                  ))}
483                </tbody>
484              </Table>
485            </CardContent>
486            <Button ghost success block>Zarządzaj załogą</Button>
487          </Card>
488          
489          {/* Statistics Card */}
490          <Card>
491            <CardTitle icon="📊">Statystyki</CardTitle>
492            <CardContent>
493              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
494                <Statistic>
495                  <StatValue primary>26</StatValue>
496                  <StatLabel>Dni w kosmosie</StatLabel>
497                </Statistic>
498                
499                <Statistic>
500                  <StatValue success>97%</StatValue>
501                  <StatLabel>Wydajność</StatLabel>
502                </Statistic>
503                
504                <Statistic>
505                  <StatValue warning pulse={fuelLevel < 30}>{fuelLevel}%</StatValue>
506                  <StatLabel>Poziom paliwa</StatLabel>
507                </Statistic>
508              </div>
509            </CardContent>
510            <Button ghost block>Zobacz więcej</Button>
511          </Card>
512          
513          {/* Alerts Card */}
514          <Card warning={alerts.length > 0}>
515            <CardTitle icon="⚠️">Alerty</CardTitle>
516            <CardContent>
517              {alerts.length === 0 ? (
518                <div style={{ textAlign: 'center', padding: '20px 0', color: theme.colors.textMuted }}>
519                  Brak aktywnych alertów
520                </div>
521              ) : (
522                <div>
523                  {alerts.map(alert => (
524                    <div key={alert.id} style={{ 
525                      marginBottom: '8px',
526                      padding: '8px',
527                      borderRadius: '4px',
528                      backgroundColor: 'rgba(0, 0, 0, 0.2)'
529                    }}>
530                      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
531                        <div>
532                          <StatusIndicator status={
533                            alert.severity === 'critical' ? 'critical' :
534                            alert.severity === 'warning' ? 'warning' :
535                            'online'
536                          } />
537                          {alert.message}
538                        </div>
539                        <div style={{ fontSize: '12px', color: theme.colors.textMuted }}>
540                          {alert.time}
541                        </div>
542                      </div>
543                    </div>
544                  ))}
545                </div>
546              )}
547            </CardContent>
548            <Button ghost warning block>Sprawdź wszystkie alerty</Button>
549          </Card>
550        </Grid>
551        
552        <WideSection>
553          <Card>
554            <CardTitle>Panel Kontrolny Misji</CardTitle>
555            <CardContent>
556              <div style={{ display: 'flex', gap: '16px', marginBottom: '20px' }}>
557                <Button primary icon="🚀">Rozpocznij Operację</Button>
558                <Button icon="📊">Analizuj Dane</Button>
559                <Button warning icon="⚠️">System Diagnostyczny</Button>
560                <Button danger icon="🚫">Procedura Awaryjna</Button>
561              </div>
562              
563              <div style={{ 
564                backgroundColor: 'rgba(0, 0, 0, 0.3)', 
565                padding: '16px', 
566                borderRadius: '8px',
567                fontFamily: theme.fonts.mono,
568                fontSize: '14px'
569              }}>
570                <div style={{ marginBottom: '8px' }}>
571                  <span style={{ color: theme.colors.success }}>system$</span> Inicjalizacja systemów głównych...
572                </div>
573                <div style={{ marginBottom: '8px' }}>
574                  <span style={{ color: theme.colors.success }}>system$</span> Sprawdzanie statusu komunikacji...
575                </div>
576                <div style={{ marginBottom: '8px' }}>
577                  <span style={{ color: theme.colors.success }}>system$</span> Łączenie z satelitami...
578                </div>
579                <div>
580                  <Spinner /> Oczekiwanie na dalsze instrukcje...
581                </div>
582              </div>
583            </CardContent>
584          </Card>
585        </WideSection>
586      </AppContainer>
587    </ThemeProvider>
588  );
589}
590
591export default SpaceDashboard;

Dobrze przemyślane rozwiązanie: Biblioteka komponentów

Jednym z najlepszych podejść do zarządzania stylami w większych projektach React jest stworzenie własnej biblioteki komponentów:

1// Plik: src/components/Button/Button.styles.js
2import styled, { css } from 'styled-components';
3
4// Reużywalne style
5const buttonSizes = {
6  small: css`
7    padding: 6px 12px;
8    font-size: 12px;
9  `,
10  medium: css`
11    padding: 8px 16px;
12    font-size: 14px;
13  `,
14  large: css`
15    padding: 12px 20px;
16    font-size: 16px;
17  `
18};
19
20const buttonVariants = {
21  primary: css`
22    background-color: ${props => props.theme.colors.primary};
23    color: white;
24    
25    &:hover {
26      background-color: ${props => props.theme.colors.primaryDark};
27    }
28  `,
29  secondary: css`
30    background-color: ${props => props.theme.colors.secondary};
31    color: white;
32    
33    &:hover {
34      background-color: ${props => props.theme.colors.secondaryDark};
35    }
36  `,
37  success: css`
38    background-color: ${props => props.theme.colors.success};
39    color: white;
40    
41    &:hover {
42      background-color: ${props => props.theme.colors.successDark};
43    }
44  `,
45  danger: css`
46    background-color: ${props => props.theme.colors.danger};
47    color: white;
48    
49    &:hover {
50      background-color: ${props => props.theme.colors.dangerDark};
51    }
52  `
53};
54
55// Główny komponent przycisk
56export const StyledButton = styled.button`
57  display: inline-flex;
58  align-items: center;
59  justify-content: center;
60  border: none;
61  border-radius: ${props => props.theme.borderRadius.sm};
62  font-family: ${props => props.theme.fonts.body};
63  font-weight: 600;
64  cursor: pointer;
65  transition: all 0.2s ease;
66  
67  // Domyślny rozmiar
68  ${buttonSizes.medium}
69  
70  // Zastosuj wybrany rozmiar
71  ${props => props.size && buttonSizes[props.size]}
72  
73  // Domyślny wariant
74  background-color: #e0e0e0;
75  color: #333;
76  
77  &:hover {
78    background-color: #d5d5d5;
79  }
80  
81  // Zastosuj wybrany wariant
82  ${props => props.variant && buttonVariants[props.variant]}
83  
84  // Ghost button style
85  ${props => props.ghost && css`
86    background-color: transparent;
87    border: 1px solid currentColor;
88    
89    &:hover {
90      background-color: rgba(0, 0, 0, 0.05);
91    }
92  `}
93  
94  // Disabled state
95  ${props => props.disabled && css`
96    opacity: 0.6;
97    cursor: not-allowed;
98    pointer-events: none;
99  `}
100  
101  // Full width button
102  ${props => props.fullWidth && css`
103    width: 100%;
104  `}
105  
106  // Icon support
107  ${props => props.iconPosition === 'left' && props.icon && css`
108    svg, img {
109      margin-right: 8px;
110    }
111  `}
112  
113  ${props => props.iconPosition === 'right' && props.icon && css`
114    svg, img {
115      margin-left: 8px;
116    }
117  `}
118  
119  // Loading state
120  ${props => props.loading && css`
121    position: relative;
122    color: transparent;
123    
124    &::after {
125      content: "";
126      position: absolute;
127      top: 50%;
128      left: 50%;
129      width: 18px;
130      height: 18px;
131      margin: -9px 0 0 -9px;
132      border: 2px solid rgba(255, 255, 255, 0.3);
133      border-radius: 50%;
134      border-top-color: white;
135      animation: buttonLoader 0.8s linear infinite;
136    }
137    
138    @keyframes buttonLoader {
139      to { transform: rotate(360deg); }
140    }
141  `}
142`;
143
144// Plik: src/components/Button/Button.jsx
145import React from 'react';
146import PropTypes from 'prop-types';
147import { StyledButton } from './Button.styles';
148
149const Button = ({
150  children,
151  variant,
152  size,
153  disabled,
154  fullWidth,
155  ghost,
156  loading,
157  icon,
158  iconPosition = 'left',
159  as,
160  ...props
161}) => {
162  return (
163    <StyledButton
164      variant={variant}
165      size={size}
166      disabled={disabled || loading}
167      fullWidth={fullWidth}
168      ghost={ghost}
169      loading={loading}
170      icon={icon}
171      iconPosition={iconPosition}
172      as={as}
173      {...props}
174    >
175      {iconPosition === 'left' && icon}
176      {children}
177      {iconPosition === 'right' && icon}
178    </StyledButton>
179  );
180};
181
182Button.propTypes = {
183  children: PropTypes.node.isRequired,
184  variant: PropTypes.oneOf(['primary', 'secondary', 'success', 'danger']),
185  size: PropTypes.oneOf(['small', 'medium', 'large']),
186  disabled: PropTypes.bool,
187  fullWidth: PropTypes.bool,
188  ghost: PropTypes.bool,
189  loading: PropTypes.bool,
190  icon: PropTypes.node,
191  iconPosition: PropTypes.oneOf(['left', 'right']),
192  as: PropTypes.elementType
193};
194
195export default Button;
196
197// Plik: src/components/index.js
198export { default as Button } from './Button/Button';
199export { default as Card } from './Card/Card';
200// Inne eksporty...
201
202// Użycie biblioteki komponentów
203import { Button, Card } from './components';
204
205function App() {
206  return (
207    <div>
208      <Card title="Panel sterowania">
209        <Button variant="primary" size="large">
210          Rozpocznij misję
211        </Button>
212        <Button variant="danger" ghost>
213          Anuluj
214        </Button>
215      </Card>
216    </div>
217  );
218}

Porównanie z innymi podejściami do stylowania

| Podejście | Zalety | Wady | |-----------|--------|------| | Styled Components | - Prawdziwy CSS w JS<br>- Izolacja stylów<br>- Dynamiczne style oparte na propsach<br>- Gotowe wsparcie dla tematyzacji | - Zwiększa rozmiar paczki JS<br>- Krzywa uczenia dla nowych deweloperów<br>- Style są renderowane w czasie rzeczywistym | | CSS Modules | - Lokalne zakresy CSS<br>- Brak runtime CSS-in-JS<br>- Znajoma składnia CSS | - Ograniczone dynamiczne style<br>- Trudniejsze warunkowe style<br>- Wymaga odpowiedniej konfiguracji narzędzi | | SCSS/SASS | - Potężne funkcje (zmienne, mixiny, itd.)<br>- Kompilowane podczas budowania<br>- Znajoma składnia | - Nie ma izolacji selektorów<br>- Trudniejsza integracja z propsami<br>- Wymaga dodatkowych narzędzi | | Tailwind CSS | - Szybki development<br>- Nie trzeba nazywać klas<br>- Spójny design system | - Rozdęte klasy HTML<br>- Utrudniona kustomizacja<br>- Stroma krzywa początkowej nauki |

Najlepsze praktyki i ograniczenia

Najlepsze praktyki

  1. Twórz reużywalne komponenty styli - unikaj powtarzania kodu CSS
  2. Używaj tematów - centralizuj wartości kolorów, odstępów, fontów itp.
  3. Unikaj zagnieżdżania - głębokie zagnieżdżanie może prowadzić do problemów z wydajnością
  4. Stosuj semantyczne nazewnictwo - nazwy komponentów powinny odzwierciedlać ich funkcję
  5. Wykorzystuj propsy dla wariantów - zamiast tworzyć oddzielne komponenty

Ograniczenia i pułapki

  1. Wydajność - przy dużej liczbie stylizowanych komponentów może wpływać na czas renderowania
  2. Server-Side Rendering - wymaga specjalnej konfiguracji dla SSR
  3. Krzywa uczenia - nowi deweloperzy muszą przyzwyczaić się do nowego paradygmatu
  4. Debugowanie - nazwy klas generowane automatycznie mogą utrudniać debugowanie

Podsumowanie

Styled Components to potężne narzędzie, które zmienia podejście do stylowania w React, umożliwiając bardziej komponentowe, modułowe i reaktywne interfejsy użytkownika. Dzięki możliwości pisania prawdziwego CSS bezpośrednio w komponentach, dynamicznego reagowania na propsy oraz łatwej tematyzacji, można tworzyć elastyczne i łatwe w utrzymaniu systemy komponentów UI.

W świecie kosmicznej eksploracji Reacta, Styled Components jest jak zaawansowany skafander kosmiczny - łączy w sobie wszystkie niezbędne narzędzia do przetrwania, jednocześnie oferując swobodę ruchu i komfort. Uczy nas, że logika i wygląd nie muszą być oddzielnymi bytami, a mogą ewoluować razem, tworząc nową jakość interfejsów użytkownika.

Vai a CodeWorlds