Kurs JavaScript i React · Moduł 7: Stylowanie i animacje

Projekt: Kosmiczny Panel Kontroli Misji

4 min czytania
W tej lekcji4

Gratulacje, Nawigatorze! Dotarłeś do końca tego modułu i nadszedł czas, aby połączyć wszystkie poznane techniki stylowania w jeden, kompleksowy projekt. Twoim zadaniem jest stworzenie Kosmicznego Panelu Kontroli Misji - interaktywnego dashboardu, który wykorzysta wiedzę z całego modułu.

Cel projektu

Zbudujesz panel kontrolny statku kosmicznego, który łączy:

  • Styled Components z dynamicznym themingiem
  • Animacje CSS z @keyframes
  • Responsywny layout z CSS Grid i Flexbox
  • Theming (tryb jasny/ciemny)
  • Interaktywne elementy z efektami hover i transition

Architektura projektu

Projekt składa się z kilku kluczowych sekcji:

1. System motywów (Theming)

Wykorzystujemy ThemeProvider ze styled-components, aby zarządzać kolorami i stylami w całej aplikacji:

1const darkTheme = {
2  bg: '#0a0a1a',
3  surface: '#12122a',
4  accent: '#00ff88',
5  text: '#e0e0e0',
6  danger: '#ff4444',
7  warning: '#ffaa00'
8};
9
10const lightTheme = {
11  bg: '#f0f4f8',
12  surface: '#ffffff',
13  accent: '#0066ff',
14  text: '#1a1a2e',
15  danger: '#cc0000',
16  warning: '#cc8800'
17};

2. Animacje CSS

Panel wykorzystuje kilka typów animacji, które poznałeś w module:

1/* Pulsowanie statusu */
2@keyframes pulse {
3  0%, 100% { opacity: 1; }
4  50% { opacity: 0.5; }
5}
6
7/* Wjazd z przesunięciem */
8@keyframes slideIn {
9  from {
10    opacity: 0;
11    transform: translateY(20px);
12  }
13  to {
14    opacity: 1;
15    transform: translateY(0);
16  }
17}
18
19/* Obracanie radaru */
20@keyframes rotate {
21  from { transform: rotate(0deg); }
22  to { transform: rotate(360deg); }
23}

3. Styled Components z propsami

Dynamiczne stylowanie komponentów na podstawie przekazanych propsów:

1const StatusBadge = styled.span`
2  padding: 4px 12px;
3  border-radius: 12px;
4  font-size: 12px;
5  font-weight: bold;
6  background: ${props => props.active ? '#00ff8833' : '#ff444433'};
7  color: ${props => props.active ? '#00ff88' : '#ff4444'};
8  animation: pulse 2s infinite;
9`;
10
11const Card = styled.div`
12  background: ${props => props.theme.surface};
13  border: 1px solid ${props => props.theme.accent}33;
14  border-radius: 12px;
15  padding: 20px;
16  transition: transform 0.3s ease, box-shadow 0.3s ease;
17
18  &:hover {
19    transform: translateY(-4px);
20    box-shadow: 0 8px 25px rgba(0, 255, 136, 0.15);
21  }
22`;

4. Responsywny layout z CSS Grid

Panel automatycznie dostosowuje się do szerokości ekranu:

1.dashboard-grid {
2  display: grid;
3  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
4  gap: 20px;
5  padding: 20px;
6}
7
8@media (max-width: 768px) {
9  .dashboard-grid {
10    grid-template-columns: 1fr;
11  }
12}

5. Kompletna struktura komponentu

Główny komponent App łączy wszystkie elementy:

1function App() {
2  const [isDark, setIsDark] = useState(true);
3
4  return (
5    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
6      <GlobalStyle />
7      <Header>
8        <Title>Mission Control</Title>
9        <ThemeToggle onClick={() => setIsDark(!isDark)}>
10          {isDark ? 'Jasny' : 'Ciemny'}
11        </ThemeToggle>
12      </Header>
13      <DashboardGrid>
14        <StatusCard systems={systemsData} />
15        <RadarCard />
16        <MissionsCard missions={missionsData} />
17        <CrewCard crew={crewData} />
18      </DashboardGrid>
19    </ThemeProvider>
20  );
21}

Kluczowe techniki zastosowane w projekcie

TechnikaZastosowanie w projekcie
Styled ComponentsWszystkie komponenty UI
ThemeProviderPrzełączanie dark/light mode
@keyframesPulsowanie statusów, obrót radaru, wjazd kart
CSS GridLayout dashboardu
FlexboxUkład wewnętrzny kart
transitionEfekty hover na kartach i przyciskach
Props dynamiczneKolory statusów, warunkowe style
Responsywnośćauto-fit i minmax w gridzie

Podsumowanie modułu

W tym module poznałeś pełne spektrum technik stylowania w React:

  1. Klasyczny CSS i CSS Modules - fundament stylowania z izolacją klas
  2. Styled Components - CSS-in-JS z dynamicznymi propsami i themingiem
  3. SCSS - preprocesor z zmiennymi, mixinami i zagnieżdżaniem
  4. Tailwind CSS - podejście utility-first dla szybkiego prototypowania
  5. Frameworki UI - gotowe biblioteki komponentów (MUI, Ant Design, Chakra)
  6. Design Systems - budowanie spójnych systemów projektowych
  7. React Transition Group - animowanie cyklu życia komponentów
  8. Animacje CSS - @keyframes, transition i zaawansowane efekty

Każda z tych technik ma swoje miejsce w arsenale kosmicznego programisty. Najlepsi inżynierowie potrafią łączyć je w spójny system - dokładnie tak, jak w tym projekcie.

Wypróbuj panel poniżej i eksperymentuj z kodem - zmień kolory motywu, dodaj nowe animacje lub rozbuduj sekcje dashboardu!

Kod do tej lekcji: App.jsx
1import React, { useState } from 'react';
2import styled, { keyframes, css, ThemeProvider, createGlobalStyle } from 'styled-components';
3
4// ===== THEMES =====
5const darkTheme = {
6  bg: '#0a0a1a',
7  surface: '#12122a',
8  surfaceHover: '#1a1a3a',
9  accent: '#00ff88',
10  accentDim: 'rgba(0,255,136,0.15)',
11  text: '#e0e0e0',
12  textSecondary: '#8888aa',
13  danger: '#ff4444',
14  warning: '#ffaa00',
15  border: 'rgba(0,255,136,0.2)',
16};
17
18const lightTheme = {
19  bg: '#f0f4f8',
20  surface: '#ffffff',
21  surfaceHover: '#e8ecf0',
22  accent: '#0066ff',
23  accentDim: 'rgba(0,102,255,0.1)',
24  text: '#1a1a2e',
25  textSecondary: '#666688',
26  danger: '#cc0000',
27  warning: '#cc8800',
28  border: 'rgba(0,102,255,0.2)',
29};
30
31// ===== GLOBAL STYLES =====
32const GlobalStyle = createGlobalStyle`
33  * { margin: 0; padding: 0; box-sizing: border-box; }
34  body {
35    font-family: 'Segoe UI', Tahoma, sans-serif;
36    background: ${props => props.theme.bg};
37    color: ${props => props.theme.text};
38    transition: background 0.3s ease, color 0.3s ease;
39  }
40`;
41
42// ===== ANIMATIONS =====
43const pulse = keyframes`
44  0%, 100% { opacity: 1; }
45  50% { opacity: 0.5; }
46`;
47
48const slideIn = keyframes`
49  from { opacity: 0; transform: translateY(20px); }
50  to { opacity: 1; transform: translateY(0); }
51`;
52
53const rotate = keyframes`
54  from { transform: rotate(0deg); }
55  to { transform: rotate(360deg); }
56`;
57
58const glow = keyframes`
59  0%, 100% { box-shadow: 0 0 5px ${() => 'rgba(0,255,136,0.3)'}; }
60  50% { box-shadow: 0 0 20px ${() => 'rgba(0,255,136,0.6)'}; }
61`;
62
63// ===== LAYOUT COMPONENTS =====
64const Header = styled.header`
65  display: flex;
66  justify-content: space-between;
67  align-items: center;
68  padding: 16px 24px;
69  background: ${p => p.theme.surface};
70  border-bottom: 1px solid ${p => p.theme.border};
71`;
72
73const Title = styled.h1`
74  font-size: 20px;
75  color: ${p => p.theme.accent};
76  letter-spacing: 2px;
77  text-transform: uppercase;
78`;
79
80const ThemeToggle = styled.button`
81  background: ${p => p.theme.accentDim};
82  border: 1px solid ${p => p.theme.accent};
83  color: ${p => p.theme.accent};
84  padding: 8px 16px;
85  border-radius: 8px;
86  cursor: pointer;
87  font-size: 14px;
88  transition: all 0.3s ease;
89  &:hover {
90    background: ${p => p.theme.accent};
91    color: ${p => p.theme.bg};
92  }
93`;
94
95const DashboardGrid = styled.div`
96  display: grid;
97  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
98  gap: 20px;
99  padding: 24px;
100`;
101
102const Card = styled.div`
103  background: ${p => p.theme.surface};
104  border: 1px solid ${p => p.theme.border};
105  border-radius: 12px;
106  padding: 20px;
107  animation: ${slideIn} 0.5s ease ${p => p.delay || '0s'} both;
108  transition: transform 0.3s ease, box-shadow 0.3s ease;
109  &:hover {
110    transform: translateY(-4px);
111    box-shadow: 0 8px 25px ${p => p.theme.accentDim};
112  }
113`;
114
115const CardTitle = styled.h3`
116  font-size: 14px;
117  text-transform: uppercase;
118  letter-spacing: 1px;
119  color: ${p => p.theme.accent};
120  margin-bottom: 16px;
121  display: flex;
122  align-items: center;
123  gap: 8px;
124`;
125
126// ===== STATUS COMPONENTS =====
127const StatusBadge = styled.span`
128  display: inline-block;
129  padding: 3px 10px;
130  border-radius: 12px;
131  font-size: 11px;
132  font-weight: bold;
133  background: ${p => p.active ? 'rgba(0,255,136,0.15)' : 'rgba(255,68,68,0.15)'};
134  color: ${p => p.active ? '#00ff88' : '#ff4444'};
135  animation: ${pulse} 2s infinite;
136`;
137
138const SystemRow = styled.div`
139  display: flex;
140  justify-content: space-between;
141  align-items: center;
142  padding: 8px 0;
143  border-bottom: 1px solid ${p => p.theme.border};
144  &:last-child { border-bottom: none; }
145`;
146
147// ===== RADAR COMPONENTS =====
148const RadarWrapper = styled.div`
149  width: 120px;
150  height: 120px;
151  border-radius: 50%;
152  border: 2px solid ${p => p.theme.accent};
153  position: relative;
154  margin: 0 auto 16px;
155  overflow: hidden;
156  background: radial-gradient(circle, ${p => p.theme.accentDim} 0%, transparent 70%);
157`;
158
159const RadarSweep = styled.div`
160  position: absolute;
161  top: 0; left: 50%;
162  width: 2px; height: 50%;
163  background: ${p => p.theme.accent};
164  transform-origin: bottom center;
165  animation: ${rotate} 3s linear infinite;
166`;
167
168const RadarDot = styled.div`
169  position: absolute;
170  width: 6px; height: 6px;
171  background: ${p => p.theme.accent};
172  border-radius: 50%;
173  top: ${p => p.y}%;
174  left: ${p => p.x}%;
175  animation: ${pulse} 1.5s infinite;
176`;
177
178// ===== PROGRESS BAR =====
179const ProgressOuter = styled.div`
180  width: 100%;
181  height: 6px;
182  background: ${p => p.theme.border};
183  border-radius: 3px;
184  margin-top: 6px;
185`;
186
187const ProgressInner = styled.div`
188  height: 100%;
189  width: ${p => p.value || 0}%;
190  background: ${p =>
191    p.value > 75 ? '#00ff88' :
192    p.value > 40 ? '#ffaa00' : '#ff4444'};
193  border-radius: 3px;
194  transition: width 0.5s ease;
195`;
196
197// ===== CREW AVATAR =====
198const AvatarCircle = styled.div`
199  width: 36px;
200  height: 36px;
201  border-radius: 50%;
202  background: ${p => p.color || p.theme.accent};
203  display: flex;
204  align-items: center;
205  justify-content: center;
206  font-size: 14px;
207  font-weight: bold;
208  color: #fff;
209`;
210
211const CrewRow = styled.div`
212  display: flex;
213  align-items: center;
214  gap: 12px;
215  padding: 8px 0;
216`;
217
218const CrewInfo = styled.div`
219  flex: 1;
220  & span { display: block; font-size: 12px; color: ${p => p.theme.textSecondary}; }
221`;
222
223// ===== DATA =====
224const systems = [
225  { name: 'Silniki warp', active: true },
226  { name: 'Osłony energetyczne', active: true },
227  { name: 'Systemy nawigacji', active: true },
228  { name: 'Komunikacja dalekiego zasięgu', active: false },
229  { name: 'Podtrzymywanie życia', active: true },
230];
231
232const missions = [
233  { name: 'Eksploracja Nebuli X-7', progress: 87 },
234  { name: 'Transport zaopatrzenia', progress: 45 },
235  { name: 'Mapowanie asteroid', progress: 62 },
236  { name: 'Naprawa stacji relay', progress: 15 },
237];
238
239const crew = [
240  { name: 'Kpt. Nova', role: 'Dowódca', color: '#e74c3c', initial: 'N' },
241  { name: 'Lt. Orion', role: 'Nawigator', color: '#3498db', initial: 'O' },
242  { name: 'Dr. Stella', role: 'Medyk', color: '#9b59b6', initial: 'S' },
243  { name: 'Ing. Vega', role: 'Inżynier', color: '#2ecc71', initial: 'V' },
244];
245
246// ===== APP =====
247function App() {
248  const [isDark, setIsDark] = useState(true);
249  const theme = isDark ? darkTheme : lightTheme;
250
251  return (
252    <ThemeProvider theme={theme}>
253      <GlobalStyle />
254      <Header>
255        <Title>Mission Control</Title>
256        <ThemeToggle onClick={() => setIsDark(!isDark)}>
257          {isDark ? 'Light Mode' : 'Dark Mode'}
258        </ThemeToggle>
259      </Header>
260
261      <DashboardGrid>
262        {/* Systems Status */}
263        <Card delay="0s">
264          <CardTitle>Systemy statku</CardTitle>
265          {systems.map((s, i) => (
266            <SystemRow key={i}>
267              <span>{s.name}</span>
268              <StatusBadge active={s.active}>
269                {s.active ? 'ONLINE' : 'OFFLINE'}
270              </StatusBadge>
271            </SystemRow>
272          ))}
273        </Card>
274
275        {/* Radar */}
276        <Card delay="0.1s">
277          <CardTitle>Radar</CardTitle>
278          <RadarWrapper>
279            <RadarSweep />
280            <RadarDot x={30} y={25} />
281            <RadarDot x={65} y={40} />
282            <RadarDot x={45} y={70} />
283          </RadarWrapper>
284          <p style={{ textAlign: 'center', fontSize: 13, opacity: 0.7 }}>
285            3 obiekty w zasięgu skanera
286          </p>
287        </Card>
288
289        {/* Missions Progress */}
290        <Card delay="0.2s">
291          <CardTitle>Misje</CardTitle>
292          {missions.map((m, i) => (
293            <div key={i} style={{ marginBottom: 12 }}>
294              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
295                <span>{m.name}</span>
296                <span style={{ fontWeight: 'bold' }}>{m.progress}%</span>
297              </div>
298              <ProgressOuter>
299                <ProgressInner value={m.progress} />
300              </ProgressOuter>
301            </div>
302          ))}
303        </Card>
304
305        {/* Crew */}
306        <Card delay="0.3s">
307          <CardTitle>Załoga</CardTitle>
308          {crew.map((c, i) => (
309            <CrewRow key={i}>
310              <AvatarCircle color={c.color}>{c.initial}</AvatarCircle>
311              <CrewInfo>
312                <strong>{c.name}</strong>
313                <span>{c.role}</span>
314              </CrewInfo>
315              <StatusBadge active={true}>Na służbie</StatusBadge>
316            </CrewRow>
317          ))}
318        </Card>
319      </DashboardGrid>
320    </ThemeProvider>
321  );
322}
323
324export default App;

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Edytor kodu

    Zbuduj kompleksowy Design System zawierający: styled-components dla component-level styling, CSS-in-JS patterns, theme provider z dark/light modes, responsive design z breakpoints, CSS Grid i Flexbox layouts, animations z @keyframes, typography system, color tokens, spacing system, oraz accessibility features (focus states, ARIA). Stwórz design system godny najbardziej zaawansowanych interfejsów Deep Space Nine!

Przydatne artykuły