Kurs JavaScript i React · Moduł 7: Stylowanie i animacje
Sass (SCSS) - Kosmiczna syntaktyczna supersiła
W tej lekcji9
W kosmosie stylowania React, Sass (Syntactically Awesome Style Sheets) jest jak statek kosmiczny nowej generacji - oferuje wszystkie możliwości standardowego CSS, ale z dodatkowymi mocami, które czynią stylowanie bardziej wydajnym, modułowym i łatwiejszym w utrzymaniu. Poruszając się w galaktyce React, warto opanować tę technologię, która może znacząco usprawnić tworzenie zaawansowanych stylizacji.
Czym jest Sass/SCSS?
Sass to preprocesor CSS, który rozszerza standardowy CSS o zmienne, zagnieżdżone reguły, mixiny, dziedziczenie selektorów i wiele innych funkcji. Istnieją dwie składnie Sass:
- Składnia indentacyjna (.sass) - oparta na wcięciach, bez nawiasów klamrowych i średników
- SCSS (.scss) - nowsza składnia, która jest nadzbiorem CSS (każdy poprawny CSS jest poprawnym SCSS)
W tym module skupimy się na SCSS, ponieważ jest bardziej popularna i łatwiejsza do nauki dla osób znających CSS. Wyobraźmy sobie SCSS jako ulepszone paliwo rakietowe, które zachowuje wszystkie właściwości standardowego paliwa, ale dodaje więcej mocy.
Konfiguracja Sass w projekcie React
Aby używać Sass w projekcie React, najpierw musimy zainstalować odpowiednie pakiety:
Create React App
Jeśli korzystasz z Create React App, instalacja jest prosta:
1npm install sassTo wszystko! CRA ma wbudowaną obsługę Sass, więc po instalacji możesz po prostu tworzyć pliki .scss/.sass i importować je do komponentów.
Vite
W projektach Vite, proces jest podobny:
1npm install sassVite ma natywne wsparcie dla Sass, więc nie potrzeba dodatkowej konfiguracji.
Inne konfiguracje (np. własny Webpack)
Jeśli tworzysz własną konfigurację, potrzebujesz:
1npm install sass sass-loaderI dodać odpowiednią konfigurację do pliku webpack.config.js:
1// Uproszczona konfiguracja webpack
2module.exports = {
3 //
4 module: {
5 rules: [
6 {
7 test: /\.s[ac]ss$/i,
8 use: [
9 'style-loader',
10 'css-loader',
11 'sass-loader',
12 ],
13 },
14 ],
15 },
16};Używanie SCSS w projekcie React
Po skonfigurowaniu, możesz tworzyć pliki SCSS i importować je do komponentów:
1// SpacePanel.jsx
2import React from 'react';
3import './SpacePanel.scss'; // Importujemy SCSS zamiast CSS
4
5function SpacePanel() {
6 return (
7 <div className="space-panel">
8 <h2 className="panel-title">Panel Sterowania Statkiem</h2>
9 <div className="control-area">
10 <div className="button-group">
11 <button className="control-button primary">Uruchom silniki</button>
12 <button className="control-button secondary">Nawigacja</button>
13 </div>
14 <div className="status-display">Status: Gotowy do startu</div>
15 </div>
16 </div>
17 );
18}
19
20export default SpacePanel;Kluczowe funkcje SCSS
1. Zmienne
W kosmicznym interfejsie, często używamy tych samych kolorów, marginesów czy fontów. Zmienne SCSS pozwalają nam zdefiniować je raz i używać wielokrotnie:
1// variables.scss
2$primary-color: #8a2be2; // Kosmiczny fiolet
3$secondary-color: #4a90e2; // Niebieski kosmosu
4$danger-color: #ff4d4f; // Czerwony alarm
5$success-color: #52c41a; // Zielony "go"
6
7$border-radius-small: 4px;
8$border-radius-medium: 8px;
9$border-radius-large: 16px;
10
11$font-main: 'Space Grotesk', sans-serif;
12$font-display: 'Nova Mono', monospace;
13
14// Używanie zmiennych
15.control-button {
16 background-color: $secondary-color;
17 border-radius: $border-radius-medium;
18 font-family: $font-main;
19
20 &.primary {
21 background-color: $primary-color;
22 }
23
24 &.danger {
25 background-color: $danger-color;
26 }
27}2. Zagnieżdżanie selektorów
Zagnieżdżanie selektorów pozwala na tworzenie bardziej czytelnej hierarchii, która odzwierciedla strukturę HTML:
1.space-panel {
2 background-color: #1a1a2e;
3 padding: 20px;
4 border-radius: 10px;
5
6 .panel-title {
7 color: white;
8 font-size: 24px;
9 margin-bottom: 15px;
10 }
11
12 .control-area {
13 display: flex;
14 justify-content: space-between;
15
16 .button-group {
17 display: flex;
18 gap: 10px;
19
20 .control-button {
21 padding: 10px 15px;
22 // Inne style przycisku...
23 }
24 }
25
26 .status-display {
27 background-color: rgba(0, 0, 0, 0.3);
28 padding: 10px;
29 border-radius: 5px;
30 }
31 }
32}Taki zapis jest nie tylko bardziej czytelny, ale również pomaga uniknąć duplikacji selektorów i czyni strukturę bardziej oczywistą.
3. Selektor & (ampersand)
Ampersand (&) reprezentuje bieżący selektor i jest szczególnie przydatny do:
Pseudoklas i pseudoelementów
1.control-button {
2 background-color: #4a90e2;
3 transition: all 0.3s;
4
5 &:hover {
6 background-color: darken(#4a90e2, 10%);
7 transform: translateY(-2px);
8 }
9
10 &:active {
11 transform: translateY(1px);
12 }
13
14 &::before {
15 content: "▲";
16 margin-right: 5px;
17 }
18}Modyfikatorów klasy
1.control-button {
2 // Bazowe style...
3
4 &.primary {
5 background-color: #8a2be2;
6 }
7
8 &.secondary {
9 background-color: #4a90e2;
10 }
11
12 &.large {
13 padding: 12px 20px;
14 font-size: 18px;
15 }
16}Łączenia selektorów
1.space-theme {
2 .control-button {
3 // Style dla .space-theme .control-button
4 }
5
6 // Używając & możemy wybierać różne kombinacje selektorów
7 &__dark .control-button {
8 // Style dla .space-theme__dark .control-button
9 }
10}4. Mixiny - wielokrotne wykorzystanie kodu
Mixiny to zbiory deklaracji CSS, które można wielokrotnie używać w kodzie. To jak reużywalne moduły statku kosmicznego:
1// mixins.scss
2@mixin flex-center {
3 display: flex;
4 justify-content: center;
5 align-items: center;
6}
7
8@mixin glass-effect {
9 background: rgba(255, 255, 255, 0.1);
10 backdrop-filter: blur(10px);
11 border: 1px solid rgba(255, 255, 255, 0.2);
12 box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
13}
14
15@mixin button-variant($bg-color, $text-color) {
16 background-color: $bg-color;
17 color: $text-color;
18
19 &:hover {
20 background-color: darken($bg-color, 10%);
21 }
22
23 &:active {
24 background-color: darken($bg-color, 15%);
25 }
26}Użycie mixinów:
1.control-panel {
2 @include glass-effect;
3 padding: 20px;
4}
5
6.button-container {
7 @include flex-center;
8 gap: 10px;
9}
10
11.launch-button {
12 @include button-variant(#ff4d4f, white);
13 padding: 10px 20px;
14}
15
16.navigation-button {
17 @include button-variant(#4a90e2, white);
18 padding: 8px 15px;
19}5. Dziedziczenie - @extend
Dziedziczenie pozwala jednej klasie dziedziczyć właściwości innej, redukując duplikację kodu:
1.base-button {
2 padding: 10px 15px;
3 border: none;
4 border-radius: 4px;
5 font-weight: bold;
6 cursor: pointer;
7 transition: all 0.3s;
8}
9
10.primary-button {
11 @extend .base-button;
12 background-color: #8a2be2;
13 color: white;
14}
15
16.secondary-button {
17 @extend .base-button;
18 background-color: #4a90e2;
19 color: white;
20}
21
22.ghost-button {
23 @extend .base-button;
24 background-color: transparent;
25 color: #8a2be2;
26 border: 1px solid currentColor;
27}6. Funkcje matematyczne i kolorystyczne
SCSS oferuje wiele wbudowanych funkcji do manipulacji kolorami i wartościami:
1.panel {
2 // Matematyczne operacje
3 width: 100% - 40px;
4 padding: 10px * 2;
5
6 // Funkcje kolorów
7 background-color: lighten(#1a1a2e, 10%);
8 border: 1px solid darken(#1a1a2e, 5%);
9
10 .warning-label {
11 color: transparentize(#ff4d4f, 0.2); // Dodaje przezroczystość
12 }
13
14 .accent-element {
15 background-color: mix(#8a2be2, #4a90e2, 30%); // Miesza kolory
16 }
17}7. Importowanie i organizacja plików
SCSS umożliwia dzielenie kodu na modułowe pliki, co jest kluczowe dla dużych projektów:
1// styles/variables.scss
2$primary-color: #8a2be2;
3// Inne zmienne...
4
5// styles/mixins.scss
6@mixin flex-center { /* ... */ }
7// Inne mixiny...
8
9// styles/buttons.scss
10@import 'variables';
11@import 'mixins';
12
13.button { /* ... */ }
14// Style przycisków...
15
16// styles/panels.scss
17@import 'variables';
18@import 'mixins';
19
20.panel { /* ... */ }
21// Style paneli...
22
23// styles/main.scss
24@import 'variables';
25@import 'mixins';
26@import 'buttons';
27@import 'panels';
28// Możemy importować wszystko razem lub wybiórczo w komponentach8. Operatory warunkowe i pętle
SCSS oferuje także konstrukcje kontrolne znane z języków programowania:
1// Warunki
2$theme: 'dark';
3
4.space-control {
5 @if $theme == 'dark' {
6 background-color: #121212;
7 color: white;
8 } @else {
9 background-color: #f5f5f5;
10 color: #333;
11 }
12}
13
14// Pętle
15$sizes: (
16 'small': 8px,
17 'medium': 16px,
18 'large': 24px,
19 'xlarge': 32px
20);
21
22@each $name, $size in $sizes {
23 .margin-#{$name} {
24 margin: $size;
25 }
26
27 .padding-#{$name} {
28 padding: $size;
29 }
30}
31
32// Generuje:
33// .margin-small { margin: 8px; }
34// .padding-small { padding: 8px; }
35// ...itd. dla wszystkich rozmiarówIntegracja SCSS z CSS Modules w React
SCSS można doskonale połączyć z CSS Modules, tworząc potężne rozwiązanie stylowania:
1// SpacePanel.module.scss
2@import 'src/styles/variables';
3@import 'src/styles/mixins';
4
5.panel {
6 @include glass-effect;
7 background-color: $panel-bg-color;
8
9 .title {
10 color: $text-primary;
11 font-family: $font-display;
12 }
13
14 .controls {
15 display: flex;
16 gap: $spacing-md;
17
18 .button {
19 @include button-base;
20
21 &.primary {
22 @include button-variant($primary-color, white);
23 }
24
25 &.secondary {
26 @include button-variant($secondary-color, white);
27 }
28 }
29 }
30}Użycie w komponencie:
1import React from 'react';
2import styles from './SpacePanel.module.scss';
3
4function SpacePanel() {
5 return (
6 <div className={styles.panel}>
7 <h2 className={styles.title}>Panel kontrolny</h2>
8 <div className={styles.controls}>
9 <button className={`${styles.button} ${styles.primary}`}>
10 Uruchom
11 </button>
12 <button className={`${styles.button} ${styles.secondary}`}>
13 Anuluj
14 </button>
15 </div>
16 </div>
17 );
18}Najlepsze praktyki używania SCSS w React
1. Struktura plików
Organizuj swoje pliki SCSS w sposób, który odzwierciedla strukturę komponentów:
1src/
2├── styles/ # Globalne style i zasoby
3│ ├── _variables.scss # Konwencja: pliki z podkreśleniem są tylko do importowania
4│ ├── _mixins.scss
5│ ├── _functions.scss
6│ ├── _reset.scss
7│ └── global.scss # Główny plik globalnych stylów
8├── components/
9│ ├── Button/
10│ │ ├── Button.jsx
11│ │ └── Button.module.scss
12│ ├── Panel/
13│ │ ├── Panel.jsx
14│ │ └── Panel.module.scss
15│ └──
16└──2. Konwencje nazewnicze
Używaj konsekwentnych konwencji nazewniczych:
- BEM (Block, Element, Modifier) - dla klas CSS
- camelCase - dla zmiennych SCSS i mixinów
- PascalCase - dla komponentów React
- kebab-case - dla plików SCSS
1// _variables.scss
2$primaryColor: #8a2be2;
3$secondaryColor: #4a90e2;
4
5// Button.module.scss (wykorzystuje BEM)
6.button {
7 // Block
8 background-color: $primaryColor;
9
10 &__icon {
11 // Element
12 margin-right: 8px;
13 }
14
15 &--large {
16 // Modifier
17 padding: 12px 24px;
18 }
19}3. Unikaj nadmiernego zagnieżdżania
Zbyt głębokie zagnieżdżenie może prowadzić do nadspecyficznych selektorów, co utrudnia ich nadpisywanie:
1// Unikaj:
2.panel {
3 .content {
4 .section {
5 .title {
6 .icon {
7 // 5 poziomów zagnieżdżenia!
8 color: red;
9 }
10 }
11 }
12 }
13}
14
15// Lepiej:
16.panel {
17 // Ogólne style panelu
18}
19
20.panel-content {
21 // Style dla zawartości
22}
23
24.section-title-icon {
25 color: red;
26}4. Używaj zmiennych dla powtarzających się wartości
1// _spacing.scss
2$spacing-xs: 4px;
3$spacing-sm: 8px;
4$spacing-md: 16px;
5$spacing-lg: 24px;
6$spacing-xl: 32px;
7
8// _colors.scss
9$blue-100: #e3f2fd;
10$blue-200: #bbdefb;
11//
12$blue-900: #0d47a1;
13
14$color-primary: $blue-500;
15$color-secondary: $purple-500;5. Twórz systemowe mixiny
1@mixin media-breakpoint-up($breakpoint) {
2 @if $breakpoint == sm {
3 @media (min-width: 576px) { @content; }
4 } @else if $breakpoint == md {
5 @media (min-width: 768px) { @content; }
6 } @else if $breakpoint == lg {
7 @media (min-width: 992px) { @content; }
8 } @else if $breakpoint == xl {
9 @media (min-width: 1200px) { @content; }
10 }
11}
12
13// Użycie:
14.responsive-element {
15 font-size: 14px;
16
17 @include media-breakpoint-up(md) {
18 font-size: 16px;
19 }
20
21 @include media-breakpoint-up(lg) {
22 font-size: 18px;
23 }
24}Praktyczny przykład: Panel sterowania statkiem kosmicznym
Zobaczmy, jak zastosować SCSS do stworzenia interaktywnego panelu sterowania:
1// _variables.scss
2$primary-color: #7b2cbf;
3$secondary-color: #3a86ff;
4$success-color: #06d6a0;
5$warning-color: #ffd166;
6$danger-color: #ef476f;
7$dark-bg: #1a1a2e;
8$panel-bg: #252941;
9$text-light: #e6e6ff;
10
11$border-radius-sm: 4px;
12$border-radius-md: 8px;
13$border-radius-lg: 12px;
14
15$transition-fast: 0.2s ease;
16$transition-normal: 0.3s ease;
17
18// _mixins.scss
19@mixin glass-morphism {
20 background: rgba(37, 41, 65, 0.8);
21 backdrop-filter: blur(10px);
22 border: 1px solid rgba(255, 255, 255, 0.1);
23 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
24}
25
26@mixin button-glow($color) {
27 box-shadow: 0 0 15px rgba($color, 0.5);
28 &:hover {
29 box-shadow: 0 0 25px rgba($color, 0.7);
30 }
31}
32
33// SpaceControlPanel.module.scss
34@import 'src/styles/variables';
35@import 'src/styles/mixins';
36
37.controlPanel {
38 @include glass-morphism;
39 padding: 24px;
40 border-radius: $border-radius-lg;
41 color: $text-light;
42
43 .panelHeader {
44 display: flex;
45 justify-content: space-between;
46 align-items: center;
47 margin-bottom: 24px;
48 padding-bottom: 16px;
49 border-bottom: 1px solid rgba(255, 255, 255, 0.1);
50
51 .title {
52 font-size: 28px;
53 font-weight: 600;
54
55 &::before {
56 content: "▲";
57 margin-right: 10px;
58 }
59 }
60
61 .status {
62 padding: 6px 12px;
63 border-radius: $border-radius-sm;
64 font-size: 14px;
65 font-weight: 500;
66
67 &.ready {
68 background-color: rgba($success-color, 0.2);
69 color: $success-color;
70 }
71
72 &.standby {
73 background-color: rgba($warning-color, 0.2);
74 color: $warning-color;
75 }
76
77 &.error {
78 background-color: rgba($danger-color, 0.2);
79 color: $danger-color;
80 }
81 }
82 }
83
84 .controlGrid {
85 display: grid;
86 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
87 gap: 20px;
88
89 .controlSection {
90 background-color: rgba(0, 0, 0, 0.2);
91 border-radius: $border-radius-md;
92 padding: 16px;
93
94 .sectionTitle {
95 font-size: 18px;
96 margin-bottom: 16px;
97 color: rgba($text-light, 0.8);
98 }
99
100 .buttonGroup {
101 display: flex;
102 flex-direction: column;
103 gap: 12px;
104 }
105 }
106 }
107
108 .button {
109 padding: 12px 16px;
110 border: none;
111 border-radius: $border-radius-sm;
112 font-weight: 500;
113 letter-spacing: 0.5px;
114 cursor: pointer;
115 transition: all $transition-normal;
116 position: relative;
117 overflow: hidden;
118
119 &::before {
120 content: "";
121 position: absolute;
122 top: 0;
123 left: -100%;
124 width: 100%;
125 height: 100%;
126 background: linear-gradient(
127 90deg,
128 transparent,
129 rgba(255, 255, 255, 0.2),
130 transparent
131 );
132 transition: all 0.6s;
133 }
134
135 &:hover::before {
136 left: 100%;
137 }
138
139 &.primary {
140 background-color: $primary-color;
141 color: white;
142 @include button-glow($primary-color);
143 }
144
145 &.secondary {
146 background-color: $secondary-color;
147 color: white;
148 @include button-glow($secondary-color);
149 }
150
151 &.success {
152 background-color: $success-color;
153 color: black;
154 @include button-glow($success-color);
155 }
156
157 &.warning {
158 background-color: $warning-color;
159 color: black;
160 @include button-glow($warning-color);
161 }
162
163 &.danger {
164 background-color: $danger-color;
165 color: white;
166 @include button-glow($danger-color);
167 }
168
169 &.disabled {
170 opacity: 0.5;
171 cursor: not-allowed;
172 pointer-events: none;
173 }
174 }
175
176 .monitorDisplay {
177 margin-top: 24px;
178 padding: 16px;
179 background-color: rgba(0, 0, 0, 0.4);
180 border-radius: $border-radius-md;
181 font-family: monospace;
182 height: 120px;
183 overflow-y: auto;
184
185 .logLine {
186 margin: 4px 0;
187 font-size: 14px;
188
189 &.info {
190 color: $secondary-color;
191 }
192
193 &.success {
194 color: $success-color;
195 }
196
197 &.warning {
198 color: $warning-color;
199 }
200
201 &.error {
202 color: $danger-color;
203 }
204
205 &::before {
206 content: "> ";
207 }
208 }
209 }
210}
211
212// Użycie w komponencie React
213import React, { useState } from 'react';
214import styles from './SpaceControlPanel.module.scss';
215
216function SpaceControlPanel() {
217 const [status, setStatus] = useState('standby');
218 const [logs, setLogs] = useState([
219 { type: 'info', text: 'System initialized' },
220 { type: 'info', text: 'Waiting for commands' }
221 ]);
222
223 const addLog = (text, type = 'info') => {
224 setLogs(prev => [...prev, { type, text }]);
225 };
226
227 const initiateSequence = () => {
228 setStatus('ready');
229 addLog('Launch sequence initiated', 'success');
230 };
231
232 const abortSequence = () => {
233 setStatus('standby');
234 addLog('Launch sequence aborted', 'warning');
235 };
236
237 const triggerAlert = () => {
238 setStatus('error');
239 addLog('ALERT: Critical system failure detected', 'error');
240 };
241
242 return (
243 <div className={styles.controlPanel}>
244 <div className={styles.panelHeader}>
245 <h2 className={styles.title}>Kosmiczny Panel Sterowania</h2>
246 <div className={`${styles.status} ${styles[status]}`}>
247 Status: {status === 'standby' ? 'Oczekiwanie' :
248 status === 'ready' ? 'Gotowy' : 'Błąd'}
249 </div>
250 </div>
251
252 <div className={styles.controlGrid}>
253 <div className={styles.controlSection}>
254 <h3 className={styles.sectionTitle}>Kontrola Startu</h3>
255 <div className={styles.buttonGroup}>
256 <button
257 className={`${styles.button} ${styles.success}`}
258 onClick={initiateSequence}
259 >
260 Inicjuj Start
261 </button>
262 <button
263 className={`${styles.button} ${styles.warning}`}
264 onClick={abortSequence}
265 >
266 Przerwij Sekwencję
267 </button>
268 </div>
269 </div>
270
271 <div className={styles.controlSection}>
272 <h3 className={styles.sectionTitle}>Systemy Pokładowe</h3>
273 <div className={styles.buttonGroup}>
274 <button
275 className={`${styles.button} ${styles.primary}`}
276 onClick={() => addLog('Navigation systems online', 'info')}
277 >
278 System Nawigacji
279 </button>
280 <button
281 className={`${styles.button} ${styles.secondary}`}
282 onClick={() => addLog('Life support activated', 'info')}
283 >
284 Podtrzymanie Życia
285 </button>
286 </div>
287 </div>
288
289 <div className={styles.controlSection}>
290 <h3 className={styles.sectionTitle}>Bezpieczeństwo</h3>
291 <div className={styles.buttonGroup}>
292 <button
293 className={`${styles.button} ${styles.danger}`}
294 onClick={triggerAlert}
295 >
296 Alarm Awaryjny
297 </button>
298 <button
299 className={`${styles.button} ${styles.disabled}`}
300 >
301 Protokół Ewakuacji
302 </button>
303 </div>
304 </div>
305 </div>
306
307 <div className={styles.monitorDisplay}>
308 {logs.map((log, index) => (
309 <div key={index} className={`${styles.logLine} ${styles[log.type]}`}>
310 {log.text}
311 </div>
312 ))}
313 </div>
314 </div>
315 );
316}
317
318export default SpaceControlPanel;Sass vs CSS-in-JS: Kiedy używać?
Zalety SCSS
- Wydajność - CSS jest generowany podczas budowania, nie w czasie rzeczywistym
- Znajoma składnia - bardzo podobna do standardowego CSS
- Wsparcie narzędzi - pełne wsparcie dla narzędzi deweloperskich (DevTools)
- Możliwość ekstrakcji CSS - można wyodrębnić style do osobnych plików CSS
- Mniejszy bundle size - nie wymaga dodatkowych bibliotek JS
Kiedy wybierać SCSS?
- W większych projektach z kompleksowymi stylami
- Gdy preferujesz separację logiki od stylu
- Gdy wydajność jest kluczowa
- Gdy pracujesz z designerami używającymi tradycyjnego CSS/SCSS
- Gdy zespół ma doświadczenie z SCSS
Podsumowanie
Sass (SCSS) to potężne narzędzie, które znacząco rozszerza możliwości CSS, pozostając przy tym bliskie jego oryginalnej składni. W ekosystemie React, SCSS doskonale integruje się zarówno z podejściem modułowym (CSS Modules), jak i z innymi technikami stylowania.
Dzięki zmiennym, mixinom, zagnieżdżaniu i innym funkcjom, SCSS pozwala na tworzenie bardziej modułowego, łatwiejszego w utrzymaniu kodu CSS, co jest szczególnie ważne w większych aplikacjach React.
Jak każdy zaawansowany statek kosmiczny, SCSS wymaga pewnego czasu na opanowanie, ale inwestycja w naukę szybko zwraca się w postaci bardziej wydajnej pracy i lepiej zorganizowanego kodu. W przypadku aplikacji React, gdzie zarządzanie komponentami i ich stylami jest kluczowe, SCSS stanowi jedną z najlepszych opcji stylowania, oferując doskonałą równowagę między mocą a prostotą.
Kod do tej lekcji: App.jsx
1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle, keyframes } from 'styled-components';
3
4// Definicja motywów
5const themes = {
6 space: {
7 primary: '#00d4ff',
8 secondary: '#ffd700',
9 background: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)',
10 cardBg: 'rgba(255, 255, 255, 0.05)',
11 text: '#ffffff',
12 textSecondary: '#a0a0a0',
13 },
14 nebula: {
15 primary: '#ff6b9d',
16 secondary: '#c44569',
17 background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
18 cardBg: 'rgba(255, 107, 157, 0.1)',
19 text: '#ffffff',
20 textSecondary: '#b0b0b0',
21 },
22 solar: {
23 primary: '#ffa502',
24 secondary: '#ff6348',
25 background: 'linear-gradient(135deg, #2c2c54 0%, #474787 50%, #706fd3 100%)',
26 cardBg: 'rgba(255, 165, 2, 0.1)',
27 text: '#ffffff',
28 textSecondary: '#c0c0c0',
29 },
30};
31
32// Global styles
33const GlobalStyle = createGlobalStyle`
34 * {
35 margin: 0;
36 padding: 0;
37 box-sizing: border-box;
38 }
39
40 body {
41 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
42 min-height: 100vh;
43 }
44`;
45
46// Animacje
47const float = keyframes`
48 0%, 100% { transform: translateY(0); }
49 50% { transform: translateY(-10px); }
50`;
51
52const pulse = keyframes`
53 0%, 100% { opacity: 1; }
54 50% { opacity: 0.7; }
55`;
56
57// Styled components
58const AppContainer = styled.div`
59 min-height: 100vh;
60 background: ${props => props.theme.background};
61 padding: 30px;
62 color: ${props => props.theme.text};
63 transition: all 0.5s ease;
64`;
65
66const Header = styled.header`
67 text-align: center;
68 margin-bottom: 40px;
69`;
70
71const Title = styled.h1`
72 font-size: 2.5em;
73 color: ${props => props.theme.primary};
74 text-shadow: 0 0 20px ${props => props.theme.primary}40;
75 animation: ${float} 3s ease-in-out infinite;
76`;
77
78const Subtitle = styled.p`
79 color: ${props => props.theme.textSecondary};
80 margin-top: 10px;
81`;
82
83const ThemeSelector = styled.div`
84 display: flex;
85 justify-content: center;
86 gap: 15px;
87 margin-bottom: 40px;
88`;
89
90const ThemeButton = styled.button`
91 padding: 12px 24px;
92 border: 2px solid ${props => props.active ? props.theme.primary : 'transparent'};
93 border-radius: 8px;
94 background: ${props => props.theme.cardBg};
95 color: ${props => props.theme.text};
96 cursor: pointer;
97 font-weight: bold;
98 transition: all 0.3s ease;
99
100 &:hover {
101 background: ${props => props.theme.primary}30;
102 transform: translateY(-2px);
103 }
104`;
105
106const CardsGrid = styled.div`
107 display: grid;
108 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
109 gap: 25px;
110 max-width: 1200px;
111 margin: 0 auto;
112`;
113
114const Card = styled.div`
115 background: ${props => props.theme.cardBg};
116 border-radius: 15px;
117 padding: 25px;
118 border: 1px solid ${props => props.theme.primary}30;
119 transition: all 0.3s ease;
120
121 &:hover {
122 transform: translateY(-5px);
123 box-shadow: 0 10px 30px ${props => props.theme.primary}20;
124 }
125`;
126
127const CardTitle = styled.h3`
128 color: ${props => props.theme.secondary};
129 margin-bottom: 15px;
130 font-size: 1.4em;
131`;
132
133const CardText = styled.p`
134 color: ${props => props.theme.textSecondary};
135 line-height: 1.6;
136 margin-bottom: 20px;
137`;
138
139const Button = styled.button`
140 padding: 10px 20px;
141 background: ${props => props.variant === 'primary'
142 ? props.theme.primary
143 : 'transparent'};
144 color: ${props => props.variant === 'primary'
145 ? '#000'
146 : props.theme.primary};
147 border: 2px solid ${props => props.theme.primary};
148 border-radius: 6px;
149 cursor: pointer;
150 font-weight: bold;
151 transition: all 0.3s ease;
152
153 &:hover {
154 background: ${props => props.theme.primary};
155 color: #000;
156 transform: scale(1.05);
157 }
158
159 &:disabled {
160 opacity: 0.5;
161 cursor: not-allowed;
162 }
163`;
164
165const StatusBadge = styled.span`
166 display: inline-block;
167 padding: 4px 12px;
168 border-radius: 20px;
169 font-size: 0.85em;
170 font-weight: bold;
171 background: ${props => {
172 switch(props.status) {
173 case 'active': return 'rgba(76, 175, 80, 0.2)';
174 case 'pending': return 'rgba(255, 152, 0, 0.2)';
175 case 'inactive': return 'rgba(244, 67, 54, 0.2)';
176 default: return 'rgba(255, 255, 255, 0.1)';
177 }
178 }};
179 color: ${props => {
180 switch(props.status) {
181 case 'active': return '#4CAF50';
182 case 'pending': return '#ff9800';
183 case 'inactive': return '#f44336';
184 default: return '#fff';
185 }
186 }};
187 animation: ${props => props.status === 'active' ? pulse : 'none'} 2s infinite;
188`;
189
190const Footer = styled.footer`
191 text-align: center;
192 margin-top: 50px;
193 color: ${props => props.theme.textSecondary};
194 padding: 20px;
195 border-top: 1px solid ${props => props.theme.primary}20;
196`;
197
198// Komponenty danych
199const missions = [
200 {
201 id: 1,
202 name: 'Eksploracja Marsa',
203 description: 'Misja badawcza na powierzchni Czerwonej Planety',
204 status: 'active',
205 },
206 {
207 id: 2,
208 name: 'Stacja Orbitalna',
209 description: 'Budowa nowej stacji kosmicznej na orbicie Ziemi',
210 status: 'pending',
211 },
212 {
213 id: 3,
214 name: 'Sonda Jowiszowa',
215 description: 'Badanie atmosfery największej planety Układu Słonecznego',
216 status: 'inactive',
217 },
218];
219
220function App() {
221 const [currentTheme, setCurrentTheme] = useState('space');
222
223 return (
224 <ThemeProvider theme={themes[currentTheme]}>
225 <GlobalStyle />
226 <AppContainer>
227 <Header>
228 <Title>Styled Components Demo</Title>
229 <Subtitle>Dynamiczne stylowanie z motywami w React</Subtitle>
230 </Header>
231
232 <ThemeSelector>
233 {Object.keys(themes).map(themeName => (
234 <ThemeButton
235 key={themeName}
236 active={currentTheme === themeName}
237 onClick={() => setCurrentTheme(themeName)}
238 >
239 {themeName.charAt(0).toUpperCase() + themeName.slice(1)}
240 </ThemeButton>
241 ))}
242 </ThemeSelector>
243
244 <CardsGrid>
245 {missions.map(mission => (
246 <Card key={mission.id}>
247 <CardTitle>{mission.name}</CardTitle>
248 <StatusBadge status={mission.status}>
249 {mission.status}
250 </StatusBadge>
251 <CardText>{mission.description}</CardText>
252 <Button variant="primary">Szczegóły</Button>
253 </Card>
254 ))}
255 </CardsGrid>
256
257 <Footer>
258 <p>Styled-components umożliwia pisanie CSS w JavaScript</p>
259 <p>z pełnym wsparciem dla motywów i dynamicznych stylów</p>
260 </Footer>
261 </AppContainer>
262 </ThemeProvider>
263 );
264}
265
266export default App;Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Która składnia SCSS jest kompatybilna z CSS?
2. Co oznacza operator & w zagnieżdżonych selektorach SCSS?
To 2 z 5 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż komendę instalacji SCSS w projekcie React:
- Klikanie w kolejności
Ułóż definicję zmiennej SCSS:
- Edytor kodu
System zmiennych SCSS
- Edytor kodu
SCSS nesting i operator &
- Klikanie w kolejności
Ułóż definicję mixina w SCSS:
- Układanie w pionie
Ułóż etapy dodawania SCSS do projektu React:
- Układanie w pionie
Ułóż elementy dyrektywy @extend w SCSS od lewej do prawej:
- Edytor kodu
SCSS mixins dla komponentów
- Układanie w pionie
Ułóż zagnieżdżony selektor SCSS z pseudoklasą hover:
- Edytor kodu
SCSS functions
- Klikanie w kolejności
Ułóż składnię BEM w SCSS od bloku do modyfikatora:
- Układanie w pionie
Uporządkuj etapy kompilacji SCSS:
- Edytor kodu
CSS Modules z SCSS