Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
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 =>
11    props.primary ? '#4a90e2' : props.danger ? '#e74c3c' : props.success ? '#2ecc71' : '#95a5a6'};
12  color: white;
13
14  /* Dynamiczny rozmiar */
15  font-size: ${props => (props.large ? '18px' : '14px')};
16  padding: ${props => (props.large ? '12px 20px' : '8px 12px')};
17
18  /* Zablokowany przycisk */
19  opacity: ${props => (props.disabled ? 0.5 : 1)};
20  cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
21
22  &:hover {
23    ${props => !props.disabled && `
24      transform: translateY(-2px);
25      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
26    `}
27  }
28`;
29
30// Użycie z różnymi propsami
31function ActionPanel() {
32  return (
33    <div>
34      <Button>Normalny</Button>
35      <Button primary>Podstawowy</Button>
36      <Button danger>Usuń</Button>
37      <Button success large>Zapisz</Button>
38      <Button disabled>Niedostępny</Button>
39    </div>
40  );
41}

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 =>
61    props.theme.colors.background === '#1a1a2e' ? '#252941' : '#ffffff'};
62  color: ${props => props.theme.colors.text};
63  border-radius: ${props => props.theme.borderRadius.medium};
64  padding: ${props => props.theme.spacing.md};
65  box-shadow: ${props => props.theme.shadows.small};
66
67  transition: all 0.3s ease;
68
69  &:hover {
70    box-shadow: ${props => props.theme.shadows.medium};
71  }
72`;
73
74// Stylizowany przycisk używający motywu
75const Button = styled.button`
76  background-color: ${props => props.theme.colors.primary};
77  color: white;
78  padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
79  border: none;
80  border-radius: ${props => props.theme.borderRadius.small};
81  font-family: ${props => props.theme.fonts.body};
82  cursor: pointer;
83
84  &:hover {
85    background-color: ${props => {
86      const color = props.theme.colors.primary;
87      // Prostą funkcję ciemniejszego koloru
88      return color.replace(/rgb((d+), (d+), (d+))/, (_, r, g, b) =>
89        `rgb(${Math.max(0, r - 20)}, ${Math.max(0, g - 20)}, ${Math.max(0, b - 20)})`
90      );
91    }};
92  }
93`;
94
95// Użycie ThemeProvider i komponentów tematycznych
96function App() {
97  const [darkMode, setDarkMode] = useState(true);
98  const theme = darkMode ? darkTheme : lightTheme;
99
100  return (
101    <ThemeProvider theme={theme}>
102      <GlobalStyle />
103      <AppContainer>
104        <Header>
105          <h1>Kosmiczne Centrum Dowodzenia</h1>
106          <Button onClick={() => setDarkMode(!darkMode)}>
107            Przełącz tryb {darkMode ? "jasny" : "ciemny"}
108          </Button>
109        </Header>
110
111        <Card>
112          <h2>Panel Kontrolny</h2>
113          <p>Tutaj możesz zarządzać swoją kosmiczną misją.</p>
114          <Button>Uruchom</Button>
115        </Card>
116      </AppContainer>
117    </ThemeProvider>
118  );
119}

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 =>
270    props.primary
271      ? props.theme.colors.primary
272      : props.danger
273      ? props.theme.colors.danger
274      : props.success
275      ? props.theme.colors.success
276      : props.warning
277      ? props.theme.colors.warning
278      : props.theme.colors.text};
279  margin-bottom: ${props => props.theme.spacing.xs};
280
281  ${props => props.pulse && `
282    animation: ${pulse} 2s infinite ease-in-out;
283  `}
284`;
285
286const StatLabel = styled.div`
287  font-size: 14px;
288  color: ${props => props.theme.colors.textMuted};
289  text-transform: uppercase;
290`;
291
292const ProgressBar = styled.div`
293  height: 8px;
294  background-color: rgba(0, 0, 0, 0.2);
295  border-radius: 4px;
296  overflow: hidden;
297  margin-bottom: ${props => props.theme.spacing.md};
298
299  &::after {
300    content: '';
301    display: block;
302    height: 100%;
303    width: ${props => `${props.value}%`};
304    background-color: ${props =>
305      props.primary
306        ? props.theme.colors.primary
307        : props.danger
308        ? props.theme.colors.danger
309        : props.success
310        ? props.theme.colors.success
311        : props.warning
312        ? props.theme.colors.warning
313        : props.theme.colors.primary};
314    transition: width 0.5s ease-in-out;
315  }
316`;
317
318const StatusIndicator = styled.span`
319  display: inline-block;
320  width: 12px;
321  height: 12px;
322  border-radius: 50%;
323  margin-right: ${props => props.theme.spacing.sm};
324  background-color: ${props =>
325    props.status === 'online'
326      ? props.theme.colors.success
327      : props.status === 'warning'
328      ? props.theme.colors.warning
329      : props.status === 'critical'
330      ? props.theme.colors.danger
331      : props.status === 'offline'
332      ? '#95a5a6'
333      : props.theme.colors.info};
334
335  ${props => props.status === 'online' && `
336    box-shadow: 0 0 10px ${props.theme.colors.success};
337  `}
338
339 ${props => props.status === 'critical' && `
340    animation: ${pulse} 1s infinite;
341  `}
342`;
343
344// ========== Table Components ==========
345const Table = styled.table`
346  width: 100%;
347  border-collapse: collapse;
348`;
349
350const TableHeader = styled.th`
351  text-align: left;
352  padding: ${props => props.theme.spacing.sm};
353  border-bottom: 2px solid ${props => props.theme.colors.border};
354  color: ${props => props.theme.colors.textMuted};
355  font-weight: 500;
356`;
357
358const TableCell = styled.td`
359  padding: ${props => props.theme.spacing.sm};
360  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
361`;
362
363const TableRow = styled.tr`
364  transition: background-color 0.2s ease;
365
366  &:hover {
367    background-color: rgba(255, 255, 255, 0.05);
368  }
369`;
370
371// ========== Loading Spinner ==========
372const Spinner = styled.div`
373  display: inline-block;
374  width: 24px;
375  height: 24px;
376  border: 3px solid rgba(255, 255, 255, 0.3);
377  border-radius: 50%;
378  border-top-color: ${props => props.theme.colors.primary};
379  animation: ${rotate} 1s linear infinite;
380  margin-right: ${props => props.theme.spacing.sm};
381`;
382
383// ========== Application Component ==========
384function SpaceDashboard() {
385  const [systemStatus, setSystemStatus] = useState('online');
386  const [fuelLevel, setFuelLevel] = useState(72);
387  const [oxygenLevel, setOxygenLevel] = useState(93);
388  const [alerts, setAlerts] = useState([
389    { id: 1, message: "Zmiana ciśnienia w module C", severity: "warning", time: "10:23" },
390    { id: 2, message: "Aktualizacja nawigacji zainstalowana", severity: "info", time: "09:15" },
391    { id: 3, message: "Cząsteczki mikrometeoroidów wykryte", severity: "warning", time: "07:42" },
392  ]);
393
394  const crewMembers = [
395    { id: 1, name: "Alex Chen", role: "Kapitan", status: "Mostek", avatar: "" },
396    { id: 2, name: "Maria Rodriguez", role: "Inżynier", status: "Maszynownia", avatar: "" },
397    { id: 3, name: "Jackson Lee", role: "Nawigator", status: "Mostek", avatar: "" },
398    { id: 4, name: "Sophia Kim", role: "Lekarz", status: "Laboratorium", avatar: "" },
399  ];
400
401  const toggleSystemStatus = () => {
402    setSystemStatus(systemStatus === 'online' ? 'warning' : 'online');
403  };
404
405  return (
406    <ThemeProvider theme={theme}>
407      <GlobalStyle />
408      <AppContainer>
409        <Header>
410          <MainTitle>Kosmiczne Centrum Dowodzenia</MainTitle>
411          <div>
412            <Button primary icon="" onClick={toggleSystemStatus}>
413              System {systemStatus === 'online' ? 'Online' : 'Uwaga'}
414            </Button>
415          </div>
416        </Header>
417
418        <Grid>
419          {/* System Status Card */}
420          <Card primary>
421            <CardTitle icon="">Status Systemów</CardTitle>
422            <CardContent>
423              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '20px' }}>
424                <div>
425                  <StatusIndicator status={systemStatus} />
426                  Status główny: {
427                    systemStatus === 'online' ? 'Wszystkie systemy sprawne' :
428                    systemStatus === 'warning' ? 'Uwaga - sprawdź alerty' :
429                    'Usterka'
430                  }
431                </div>
432              </div>
433
434              <div>
435                <div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between' }}>
436                  <span>Poziom paliwa</span>
437                  <span>{fuelLevel}%</span>
438                </div>
439                <ProgressBar
440                  value={fuelLevel}
441                  warning={fuelLevel < 50}
442                  danger={fuelLevel < 20}
443                  success={fuelLevel >= 50}
444                />
445
446                <div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between' }}>
447                  <span>Poziom tlenu</span>
448                  <span>{oxygenLevel}%</span>
449                </div>
450                <ProgressBar
451                  value={oxygenLevel}
452                  warning={oxygenLevel < 70}
453                  danger={oxygenLevel < 40}
454                  success={oxygenLevel >= 70}
455                />
456              </div>
457            </CardContent>
458            <Button ghost primary block>Szczegółowe statystyki</Button>
459          </Card>
460
461          {/* Crew Status Card */}
462          <Card success>
463            <CardTitle icon="">Status Załogi</CardTitle>
464            <CardContent>
465              <Table>
466                <thead>
467                  <tr>
468                    <TableHeader></TableHeader>
469                    <TableHeader>Imię</TableHeader>
470                    <TableHeader>Lokalizacja</TableHeader>
471                  </tr>
472                </thead>
473                <tbody>
474                  {crewMembers.map(member => (
475                    <TableRow key={member.id}>
476                      <TableCell>{member.avatar}</TableCell>
477                      <TableCell>
478                        {member.name}
479                        <div style={{ fontSize: '12px', color: theme.colors.textMuted }}>
480                          {member.role}
481                        </div>
482                      </TableCell>
483                      <TableCell>{member.status}</TableCell>
484                    </TableRow>
485                  ))}
486                </tbody>
487              </Table>
488            </CardContent>
489            <Button ghost success block>Zarządzaj załogą</Button>
490          </Card>
491
492          {/* Statistics Card */}
493          <Card>
494            <CardTitle icon="">Statystyki</CardTitle>
495            <CardContent>
496              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
497                <Statistic>
498                  <StatValue primary>26</StatValue>
499                  <StatLabel>Dni w kosmosie</StatLabel>
500                </Statistic>
501
502                <Statistic>
503                  <StatValue success>97%</StatValue>
504                  <StatLabel>Wydajność</StatLabel>
505                </Statistic>
506
507                <Statistic>
508                  <StatValue warning pulse={fuelLevel < 30}>{fuelLevel}%</StatValue>
509                  <StatLabel>Poziom paliwa</StatLabel>
510                </Statistic>
511              </div>
512            </CardContent>
513            <Button ghost block>Zobacz więcej</Button>
514          </Card>
515
516          {/* Alerts Card */}
517          <Card warning={alerts.length > 0}>
518            <CardTitle icon="">Alerty</CardTitle>
519            <CardContent>
520              {alerts.length === 0 ? (
521                <div style={{ textAlign: 'center', padding: '20px 0', color: theme.colors.textMuted }}>
522                  Brak aktywnych alertów
523                </div>
524              ) : (
525                <div>
526                  {alerts.map(alert => (
527                    <div key={alert.id} style={{
528                      marginBottom: '8px',
529                      padding: '8px',
530                      borderRadius: '4px',
531                      backgroundColor: 'rgba(0, 0, 0, 0.2)'
532                    }}>
533                      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
534                        <div>
535                          <StatusIndicator status={
536                            alert.severity === 'critical' ? 'critical' :
537                            alert.severity === 'warning' ? 'warning' :
538                            'online'
539                          } />
540                          {alert.message}
541                        </div>
542                        <div style={{ fontSize: '12px', color: theme.colors.textMuted }}>
543                          {alert.time}
544                        </div>
545                      </div>
546                    </div>
547                  ))}
548                </div>
549              )}
550            </CardContent>
551            <Button ghost warning block>Sprawdź wszystkie alerty</Button>
552          </Card>
553        </Grid>
554
555        <WideSection>
556          <Card>
557            <CardTitle>Panel Kontrolny Misji</CardTitle>
558            <CardContent>
559              <div style={{ display: 'flex', gap: '16px', marginBottom: '20px' }}>
560                <Button primary icon="">Rozpocznij Operację</Button>
561                <Button icon="">Analizuj Dane</Button>
562                <Button warning icon="">System Diagnostyczny</Button>
563                <Button danger icon="">Procedura Awaryjna</Button>
564              </div>
565
566              <div style={{
567                backgroundColor: 'rgba(0, 0, 0, 0.3)',
568                padding: '16px',
569                borderRadius: '8px',
570                fontFamily: theme.fonts.mono,
571                fontSize: '14px'
572              }}>
573                <div style={{ marginBottom: '8px' }}>
574                  <span style={{ color: theme.colors.success }}>system$</span> Inicjalizacja systemów głównych...
575                </div>
576                <div style={{ marginBottom: '8px' }}>
577                  <span style={{ color: theme.colors.success }}>system$</span> Sprawdzanie statusu komunikacji...
578                </div>
579                <div style={{ marginBottom: '8px' }}>
580                  <span style={{ color: theme.colors.success }}>system$</span> Łączenie z satelitami...
581                </div>
582                <div>
583                  <Spinner /> Oczekiwanie na dalsze instrukcje...
584                </div>
585              </div>
586            </CardContent>
587          </Card>
588        </WideSection>
589      </AppContainer>
590    </ThemeProvider>
591  );
592}
593
594export 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.

Przejdź do CodeWorlds