Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Tworzenie własnych systemów design - Architektura wizualna kosmicznej przyszłości

W naszej międzygalaktycznej podróży przez świat Reacta, dotarliśmy do jednego z najważniejszych aspektów budowania zaawansowanych interfejsów - systemów design. Jeśli frameworki komponentów są jak gotowe stacje kosmiczne, to systemy design są jak plany do budowy całych cywilizacji - spójnych, funkcjonalnych i skalowalnych.

Czym jest system design?

System design (system projektowy) to zbiór standardów, komponentów i wytycznych, które definiują sposób tworzenia interfejsów użytkownika w całej aplikacji lub ekosystemie produktów. To jak kodeks budowlany dla kosmicznych stacji i pojazdów - zapewnia, że wszystkie elementy pasują do siebie, tworząc spójną, funkcjonalną i estetyczną całość.

Dobry system design obejmuje:

  1. Zasady projektowe - fundamentalne wartości i filozofia designu
  2. System komponentów - biblioteka wielokrotnego użytku elementów UI
  3. Tokeny designu - standaryzowane wartości dla kolorów, typografii, odstępów itp.
  4. Wzorce interakcji - standardowe sposoby interakcji użytkownika z interfejsem
  5. Dokumentację - instrukcje i wytyczne dla zespołu

Dlaczego warto tworzyć własny system design?

Tworzenie własnego systemu design może wydawać się dużym wyzwaniem, ale przynosi liczne korzyści:

  • Spójność - wszystkie elementy interfejsu mówią tym samym wizualnym językiem
  • Skalowalność - łatwiejsze rozwijanie aplikacji i dodawanie nowych funkcji
  • Efektywność - szybsze projektowanie i implementacja dzięki gotowym komponentom
  • Lepsza współpraca - wspólny język i standardy dla projektantów i deweloperów
  • Tożsamość marki - unikalne i rozpoznawalne doświadczenie użytkownika

Budowanie systemu design od podstaw

Zbudowanie własnego systemu design jest jak terraformowanie nowej planety - wymaga wizji, planowania i systematycznego podejścia. Przejdźmy przez kluczowe kroki tego procesu.

1. Definiowanie zasad projektowych

Zacznij od fundamentalnych zasad, które będą kierować całym systemem. To jak konstytucja twojej kosmicznej cywilizacji. Przykładowe zasady:

  • Prostota - Eliminuj złożoność, gdy tylko to możliwe
  • Spójność - Podobne problemy rozwiązuj w podobny sposób
  • Dostępność - Interfejs musi być użyteczny dla wszystkich
  • Wydajność - Optymalizuj dla szybkości i responsywności
  • Inkluzywność - Projektuj z myślą o różnorodnych potrzebach

2. Tworzenie tokenów designu

Tokeny designu to zmienne, które przechowują podstawowe wartości stylistyczne. To jak pierwiastki chemiczne, z których zbudowany jest twój świat.

Kolory

1// design-tokens.js
2
3export const colors = {
4  // Podstawowa paleta
5  primary: {
6    50: '#e3f2fd',
7    100: '#bbdefb',
8    200: '#90caf9',
9    300: '#64b5f6',
10    400: '#42a5f5',
11    500: '#2196f3',  // Główny kolor
12    600: '#1e88e5',
13    700: '#1976d2',
14    800: '#1565c0',
15    900: '#0d47a1',
16  },
17  
18  // Kolory semantyczne
19  success: '#4caf50',
20  warning: '#ff9800',
21  error: '#f44336',
22  info: '#2196f3',
23  
24  // Neutralne
25  neutral: {
26    50: '#fafafa',
27    100: '#f5f5f5',
28    200: '#eeeeee',
29    300: '#e0e0e0',
30    400: '#bdbdbd',
31    500: '#9e9e9e',
32    600: '#757575',
33    700: '#616161',
34    800: '#424242',
35    900: '#212121',
36  },
37};

Typografia

1// design-tokens.js
2
3export const typography = {
4  fontFamily: {
5    base: '"Inter", system-ui, -apple-system, sans-serif',
6    heading: '"Space Grotesk", sans-serif',
7    monospace: '"Fira Code", monospace',
8  },
9  
10  fontSize: {
11    xs: '0.75rem',     // 12px
12    sm: '0.875rem',    // 14px
13    base: '1rem',      // 16px
14    lg: '1.125rem',    // 18px
15    xl: '1.25rem',     // 20px
16    '2xl': '1.5rem',   // 24px
17    '3xl': '1.875rem', // 30px
18    '4xl': '2.25rem',  // 36px
19    '5xl': '3rem',     // 48px
20  },
21  
22  fontWeight: {
23    light: 300,
24    regular: 400,
25    medium: 500,
26    semibold: 600,
27    bold: 700,
28  },
29  
30  lineHeight: {
31    none: 1,
32    tight: 1.25,
33    snug: 1.375,
34    normal: 1.5,
35    relaxed: 1.625,
36    loose: 2,
37  },
38};

Przestrzeń

1// design-tokens.js
2
3export const spacing = {
4  px: '1px',
5  0: '0',
6  0.5: '0.125rem',  // 2px
7  1: '0.25rem',     // 4px
8  1.5: '0.375rem',  // 6px
9  2: '0.5rem',      // 8px
10  2.5: '0.625rem',  // 10px
11  3: '0.75rem',     // 12px
12  3.5: '0.875rem',  // 14px
13  4: '1rem',        // 16px
14  5: '1.25rem',     // 20px
15  6: '1.5rem',      // 24px
16  8: '2rem',        // 32px
17  10: '2.5rem',     // 40px
18  12: '3rem',       // 48px
19  16: '4rem',       // 64px
20  20: '5rem',       // 80px
21  24: '6rem',       // 96px
22  32: '8rem',       // 128px
23  40: '10rem',      // 160px
24  48: '12rem',      // 192px
25  56: '14rem',      // 224px
26  64: '16rem',      // 256px
27};

Cienie

1// design-tokens.js
2
3export const shadows = {
4  sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
5  md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
6  lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
7  xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
8  '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
9  inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
10  outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
11  none: 'none',
12};

Breakpointy

1// design-tokens.js
2
3export const breakpoints = {
4  xs: '0px',
5  sm: '600px',
6  md: '960px',
7  lg: '1280px',
8  xl: '1920px',
9};

3. Budowanie atomowych komponentów

Teraz przejdźmy do budowania podstawowych, atomowych komponentów, które będą stanowić fundamentalne bloki twojego systemu. To jak tworzenie pierwszych budynków na nowej planecie.

Przycisk (Button)

1import React from 'react';
2import styled, { css } from 'styled-components';
3import { colors, typography, spacing } from '../design-tokens';
4
5// Definiujemy warianty przycisku
6const variants = {
7  primary: css`
8    background-color: ${colors.primary[500]};
9    color: white;
10    
11    &:hover {
12      background-color: ${colors.primary[600]};
13    }
14    
15    &:focus {
16      box-shadow: 0 0 0 3px ${colors.primary[300]};
17    }
18  `,
19  
20  secondary: css`
21    background-color: white;
22    color: ${colors.primary[500]};
23    border: 1px solid ${colors.primary[500]};
24    
25    &:hover {
26      background-color: ${colors.primary[50]};
27    }
28    
29    &:focus {
30      box-shadow: 0 0 0 3px ${colors.primary[300]};
31    }
32  `,
33  
34  danger: css`
35    background-color: ${colors.error};
36    color: white;
37    
38    &:hover {
39      background-color: #d32f2f;
40    }
41    
42    &:focus {
43      box-shadow: 0 0 0 3px rgba(244, 67, 54, 0.4);
44    }
45  `,
46};
47
48// Definiujemy rozmiary przycisków
49const sizes = {
50  small: css`
51    padding: ${spacing[1]} ${spacing[2]};
52    font-size: ${typography.fontSize.sm};
53  `,
54  
55  medium: css`
56    padding: ${spacing[2]} ${spacing[4]};
57    font-size: ${typography.fontSize.base};
58  `,
59  
60  large: css`
61    padding: ${spacing[3]} ${spacing[6]};
62    font-size: ${typography.fontSize.lg};
63  `,
64};
65
66// Komponent przycisku
67const StyledButton = styled.button`
68  font-family: ${typography.fontFamily.base};
69  font-weight: ${typography.fontWeight.medium};
70  border-radius: 4px;
71  border: none;
72  cursor: pointer;
73  transition: all 0.2s ease-in-out;
74  display: inline-flex;
75  align-items: center;
76  justify-content: center;
77  
78  &:disabled {
79    opacity: 0.6;
80    cursor: not-allowed;
81  }
82  
83  /* Zastosuj wariant */
84  ${props => variants[props.variant || 'primary']}
85  
86  /* Zastosuj rozmiar */
87  ${props => sizes[props.size || 'medium']}
88`;
89
90const Button = ({ children, variant, size, ...rest }) => (
91  <StyledButton variant={variant} size={size} {...rest}>
92    {children}
93  </StyledButton>
94);
95
96export default Button;

Karta (Card)

1import React from 'react';
2import styled from 'styled-components';
3import { spacing, shadows, colors } from '../design-tokens';
4
5const StyledCard = styled.div`
6  background-color: white;
7  border-radius: 8px;
8  box-shadow: ${shadows.md};
9  overflow: hidden;
10`;
11
12const CardHeader = styled.div`
13  padding: ${spacing[4]} ${spacing[4]} ${spacing[2]};
14  border-bottom: 1px solid ${colors.neutral[200]};
15`;
16
17const CardTitle = styled.h3`
18  margin: 0;
19  font-size: ${props => props.theme.typography.fontSize.xl};
20  font-weight: ${props => props.theme.typography.fontWeight.semibold};
21`;
22
23const CardSubtitle = styled.div`
24  color: ${colors.neutral[600]};
25  margin-top: ${spacing[1]};
26`;
27
28const CardContent = styled.div`
29  padding: ${spacing[4]};
30`;
31
32const CardFooter = styled.div`
33  padding: ${spacing[2]} ${spacing[4]};
34  background-color: ${colors.neutral[50]};
35  border-top: 1px solid ${colors.neutral[200]};
36  display: flex;
37  justify-content: flex-end;
38`;
39
40const Card = ({ children, ...rest }) => {
41  return <StyledCard {...rest}>{children}</StyledCard>;
42};
43
44Card.Header = CardHeader;
45Card.Title = CardTitle;
46Card.Subtitle = CardSubtitle;
47Card.Content = CardContent;
48Card.Footer = CardFooter;
49
50export default Card;

4. Budowanie złożonych komponentów

Gdy masz już podstawowe komponenty, możesz zacząć budować bardziej złożone elementy interfejsu, wykorzystując atomowe komponenty jako budulce. To jak tworzenie zaawansowanych struktur po zbudowaniu fundamentów.

Dialog

1import React, { useEffect, useRef } from 'react';
2import styled from 'styled-components';
3import Button from './Button';
4import { colors, spacing, shadows } from '../design-tokens';
5
6const Overlay = styled.div`
7  position: fixed;
8  top: 0;
9  left: 0;
10  right: 0;
11  bottom: 0;
12  background-color: rgba(0, 0, 0, 0.5);
13  display: flex;
14  align-items: center;
15  justify-content: center;
16  z-index: 1000;
17`;
18
19const DialogContainer = styled.div`
20  background-color: white;
21  border-radius: 8px;
22  box-shadow: ${shadows.xl};
23  width: 100%;
24  max-width: ${props =>
25    props.size === 'small' ? '400px' : props.size === 'large' ? '800px' : '600px'};
26  max-height: 90vh;
27  overflow-y: auto;
28  display: flex;
29  flex-direction: column;
30`;
31
32const DialogHeader = styled.div`
33  padding: ${spacing[4]};
34  border-bottom: 1px solid ${colors.neutral[200]};
35  display: flex;
36  align-items: center;
37  justify-content: space-between;
38`;
39
40const DialogTitle = styled.h3`
41  margin: 0;
42  font-weight: 600;
43`;
44
45const CloseButton = styled.button`
46  background: none;
47  border: none;
48  cursor: pointer;
49  color: ${colors.neutral[500]};
50  font-size: 1.5rem;
51  line-height: 1;
52  
53  &:hover {
54    color: ${colors.neutral[700]};
55  }
56`;
57
58const DialogContent = styled.div`
59  padding: ${spacing[4]};
60  flex: 1;
61`;
62
63const DialogFooter = styled.div`
64  padding: ${spacing[3]} ${spacing[4]};
65  border-top: 1px solid ${colors.neutral[200]};
66  display: flex;
67  justify-content: flex-end;
68  gap: ${spacing[2]};
69`;
70
71const Dialog = ({ 
72  isOpen, 
73  onClose, 
74  title, 
75  children, 
76  size = 'medium',
77  confirmText = 'Potwierdź',
78  cancelText = 'Anuluj',
79  onConfirm,
80  hideActions = false
81}) => {
82  const dialogRef = useRef(null);
83  
84  // Zamykanie dialogu po kliknięciu ESC
85  useEffect(() => {
86    const handleKeyDown = (e) => {
87      if (e.key === 'Escape') {
88        onClose();
89      }
90    };
91    
92    if (isOpen) {
93      window.addEventListener('keydown', handleKeyDown);
94    }
95    
96    return () => {
97      window.removeEventListener('keydown', handleKeyDown);
98    };
99  }, [isOpen, onClose]);
100  
101  // Zatrzymanie propagacji kliknięcia wewnątrz dialogu
102  const handleDialogClick = (e) => {
103    e.stopPropagation();
104  };
105  
106  if (!isOpen) return null;
107  
108  return (
109    <Overlay onClick={onClose}>
110      <DialogContainer size={size} onClick={handleDialogClick} ref={dialogRef}>
111        <DialogHeader>
112          <DialogTitle>{title}</DialogTitle>
113          <CloseButton onClick={onClose}>&times;</CloseButton>
114        </DialogHeader>
115        <DialogContent>{children}</DialogContent>
116        {!hideActions && (
117          <DialogFooter>
118            <Button variant="secondary" onClick={onClose}>
119              {cancelText}
120            </Button>
121            {onConfirm && (
122              <Button variant="primary" onClick={onConfirm}>
123                {confirmText}
124              </Button>
125            )}
126          </DialogFooter>
127        )}
128      </DialogContainer>
129    </Overlay>
130  );
131};
132
133export default Dialog;

5. Implementacja wzorców interakcji

Wzorce interakcji to standardowe sposoby, w jakie użytkownicy wchodzą w interakcję z interfejsem. Oto przykład implementacji wzorca "Powiadomienia" (Toast):

1import React, { useState, useEffect } from 'react';
2import styled, { css, keyframes } from 'styled-components';
3import { colors, spacing, shadows } from '../design-tokens';
4
5// Animacja wejścia
6const slideIn = keyframes`
7  from {
8    transform: translateX(100%);
9    opacity: 0;
10  }
11  to {
12    transform: translateX(0);
13    opacity: 1;
14  }
15`;
16
17// Animacja wyjścia
18const slideOut = keyframes`
19  from {
20    transform: translateX(0);
21    opacity: 1;
22  }
23  to {
24    transform: translateX(100%);
25    opacity: 0;
26  }
27`;
28
29// Różne typy powiadomień
30const toastTypes = {
31  success: css`
32    background-color: ${colors.success};
33    color: white;
34  `,
35  error: css`
36    background-color: ${colors.error};
37    color: white;
38  `,
39  warning: css`
40    background-color: ${colors.warning};
41    color: white;
42  `,
43  info: css`
44    background-color: ${colors.info};
45    color: white;
46  `,
47};
48
49const ToastContainer = styled.div`
50  position: fixed;
51  top: ${spacing[4]};
52  right: ${spacing[4]};
53  z-index: 1500;
54  display: flex;
55  flex-direction: column;
56  gap: ${spacing[2]};
57`;
58
59const Toast = styled.div`
60  min-width: 300px;
61  max-width: 400px;
62  padding: ${spacing[3]};
63  border-radius: 4px;
64  box-shadow: ${shadows.lg};
65  display: flex;
66  align-items: center;
67  justify-content: space-between;
68  animation: ${props => (props.leaving ? slideOut : slideIn)} 0.3s ease-in-out forwards;
69  
70  ${props => toastTypes[props.type || 'info']}
71`;
72
73const CloseButton = styled.button`
74  background: none;
75  border: none;
76  color: inherit;
77  opacity: 0.7;
78  cursor: pointer;
79  font-size: 1.25rem;
80  
81  &:hover {
82    opacity: 1;
83  }
84`;
85
86// Kontekst dla powiadomień
87const ToastContext = React.createContext();
88
89// Provider powiadomień
90export const ToastProvider = ({ children }) => {
91  const [toasts, setToasts] = useState([]);
92  
93  const addToast = (message, type = 'info', duration = 5000) => {
94    const id = Date.now();
95    setToasts(prev => [...prev, { id, message, type, duration }]);
96    
97    if (duration !== Infinity) {
98      setTimeout(() => removeToast(id), duration);
99    }
100    
101    return id;
102  };
103  
104  const removeToast = (id) => {
105    setToasts(prev => {
106      const toast = prev.find(t => t.id === id);
107      if (toast) {
108        // Oznaczenie tosta jako wychodzącego
109        return prev.map(t => t.id === id ? { ...t, leaving: true } : t);
110      }
111      return prev;
112    });
113    
114    // Usuń toast po zakończeniu animacji
115    setTimeout(() => {
116      setToasts(prev => prev.filter(t => t.id !== id));
117    }, 300); // Czas trwania animacji
118  };
119  
120  return (
121    <ToastContext.Provider value={{ addToast, removeToast }}>
122      {children}
123      <ToastContainer>
124        {toasts.map(toast => (
125          <Toast key={toast.id} type={toast.type} leaving={toast.leaving}>
126            <span>{toast.message}</span>
127            <CloseButton onClick={() => removeToast(toast.id)}>&times;</CloseButton>
128          </Toast>
129        ))}
130      </ToastContainer>
131    </ToastContext.Provider>
132  );
133};
134
135// Hook do używania powiadomień
136export const useToast = () => {
137  const context = React.useContext(ToastContext);
138  if (!context) {
139    throw new Error('useToast must be used within a ToastProvider');
140  }
141  return context;
142};
143
144// Przykład użycia:
145// const { addToast } = useToast();
146// addToast('Operacja zakończona sukcesem!', 'success');

6. Dokumentacja systemu design

Ostatnim, ale nie mniej ważnym krokiem, jest stworzenie dokumentacji, która pozwoli innym deweloperom i projektantom korzystać z twojego systemu design. Popularnymi narzędziami do tworzenia dokumentacji są:

  • Storybook - interaktywna dokumentacja komponentów
  • Docusaurus - generator stron dokumentacji
  • Docz - narzędzie dedykowane do dokumentacji komponentów React

Przykład konfiguracji komponentu w Storybook:

1// Button.stories.js
2
3import React from 'react';
4import Button from './Button';
5
6export default {
7  title: 'Atoms/Button',
8  component: Button,
9  argTypes: {
10    variant: {
11      control: { type: 'select', options: ['primary', 'secondary', 'danger'] },
12      description: 'Wariant przycisku określający jego styl i przeznaczenie',
13      table: {
14        type: { summary: 'string' },
15        defaultValue: { summary: 'primary' },
16      },
17    },
18    size: {
19      control: { type: 'select', options: ['small', 'medium', 'large'] },
20      description: 'Rozmiar przycisku',
21      table: {
22        type: { summary: 'string' },
23        defaultValue: { summary: 'medium' },
24      },
25    },
26    disabled: {
27      control: 'boolean',
28      description: 'Czy przycisk jest wyłączony',
29      table: {
30        type: { summary: 'boolean' },
31        defaultValue: { summary: false },
32      },
33    },
34  },
35};
36
37// Podstawowe użycie
38export const Primary = () => <Button variant="primary">Przycisk główny</Button>;
39
40// Warianty
41export const Variants = () => (
42  <div style={{ display: 'flex', gap: '8px' }}>
43    <Button variant="primary">Główny</Button>
44    <Button variant="secondary">Drugorzędny</Button>
45    <Button variant="danger">Niebezpieczny</Button>
46  </div>
47);
48
49// Rozmiary
50export const Sizes = () => (
51  <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
52    <Button size="small">Mały</Button>
53    <Button size="medium">Średni</Button>
54    <Button size="large">Duży</Button>
55  </div>
56);
57
58// Stan wyłączony
59export const Disabled = () => <Button disabled>Przycisk wyłączony</Button>;

Strategie wdrażania systemu design

Wdrożenie systemu design w istniejącym projekcie to jak modernizacja działającej stacji kosmicznej - wymaga planowania i stopniowego podejścia:

  1. Inkrementalne wdrażanie - zacznij od małych, izolowanych komponentów
  2. Równoległy rozwój - najpierw zbuduj system w oddzielnej gałęzi lub projekcie
  3. Migracja stronami - migruj jedną stronę lub sekcję na raz
  4. Komponent po komponencie - zastępuj stare komponenty nowymi jeden po drugim

Najlepsze praktyki w tworzeniu systemów design

  • Zacznij od badań - analizuj konkurencję i sprawdzone wzorce
  • Współpracuj od początku - włącz projektantów i deweloperów
  • Myśl systemowo - projektuj system, a nie pojedyncze ekrany
  • Testuj wcześnie i często - zbieraj feedback od użytkowników
  • Dokumentuj wszystko - nie tylko kod, ale też decyzje i uzasadnienia
  • Ewoluuj, nie rewolucjonizuj - system design powinien być żywym, ewoluującym bytem

Zadanie praktyczne: Stwórz mikro-system design dla aplikacji kosmicznej

Zaprojektuj i zaimplementuj mały system design dla aplikacji do monitorowania kosmicznych misji. Twój system powinien zawierać:

  1. Tokeny designu - zdefiniuj podstawowe zmienne dla kolorów, typografii i odstępów
  2. Komponenty atomowe - stwórz przynajmniej 3 podstawowe komponenty (np. Button, Card, Input)
  3. Jeden złożony komponent - wykorzystaj komponenty atomowe do stworzenia bardziej złożonego komponentu (np. Dashboard Card, Mission Status)
  4. Dokumentację - opisz swój system i komponenty

Pamiętaj, że dobry system design to nie tylko estetyka, ale przede wszystkim funkcjonalność, spójność i łatwość użycia. Twórz system z myślą o rozwoju aplikacji w przyszłości.

Gdy tworzysz własny system design, stajesz się nie tylko programistą, ale również architektem kosmicznej przyszłości interfejsów. Podobnie jak twórcy pierwszych kolonii na innych planetach, tworzysz fundamenty, na których będą budować przyszłe pokolenia deweloperów i użytkowników.

Vai a CodeWorlds