design/raycast

4 min czytania 973 słów
design/raycast screenshot

Raycast: Sztuka interfejsu produktywności

„Wierzymy, że najlepsze narzędzia nie przeszkadzają w pracy." — Zespół Raycast

Raycast to launcher dla macOS, który zastąpił Spotlight wśród zaawansowanych użytkowników. Pokazuje, jak projektować pod kątem ekstremalnej wydajności, zachowując jednocześnie ciepło i osobowość.


Dlaczego Raycast ma znaczenie

Raycast udowadnia, że utylitarne oprogramowanie nie musi być zimne. Łączy: - Błyskawiczną szybkość (uruchomienie w <50ms) - Interakcję priorytetowo z klawiatury - Rozszerzalność przez sklep - Osobowość poprzez przyjemne detale

Kluczowe osiągnięcia: - Na nowo zdefiniował, czym może być launcher - Udowodnił, że narzędzia deweloperskie mogą mieć osobowość - Stworzył prosperujący ekosystem rozszerzeń - Ustanowił nowe standardy dla natywnego designu macOS


Kluczowe wnioski

  1. 50ms to próg szybkości - Użytkownicy zauważają każde opóźnienie powyżej 50ms; traktuj to jako twarde ograniczenie inżynieryjne, nie wytyczną
  2. Natywna integracja z platformą buduje zaufanie - Vibrancy, SF Symbols, systemowe kolory akcentów i standardowe skróty sprawiają, że Raycast czuje się jak część macOS
  3. Skróty klawiszowe muszą być odkrywalne - Pokazuj skróty bezpośrednio w wynikach (⌘1, ⌘2), nie ukryte w tooltipach czy dokumentacji
  4. Osobowość bez kosztu wydajności - Konfetti przy osiągnięciach, zabawne puste stany i sprytne teksty tworzą radość bez dodawania opóźnień
  5. Ekosystemy rozszerzeń potrzebują języka projektowego - Rozszerzenia zewnętrzne wyglądają natywnie, ponieważ używają tych samych komponentów formularzy, paneli akcji i wzorców nawigacji

Podstawowe zasady projektowania

1. Natychmiastowa odpowiedź

Raycast czuje się jak przedłużenie twoich myśli. Bez opóźnień. Bez czekania.

Jak to osiągają: - Natywna implementacja w Swift (nie Electron) - Wstępnie załadowany indeks rozszerzeń - Agresywne cache'owanie - Zoptymalizowany potok renderowania

Zasada 50ms: Jeśli coś trwa dłużej niż 50ms, użytkownicy to zauważają. Raycast traktuje to jako twarde ograniczenie.

Implikacje projektowe: - Pokazuj UI natychmiast, ładuj dane asynchronicznie - Stany szkieletowe dla ciężkich operacji - Nigdy nie blokuj głównego wątku - Preferuj lokalne przetwarzanie nad wywołania sieciowe

// Wzorzec natychmiastowej odpowiedzi w stylu Raycast
struct SearchView: View {
    @State private var results: [Result] = []
    @State private var isLoading = false

    var body: some View {
        VStack {
            // Pokaż natychmiast z danymi z cache
            ForEach(cachedResults) { result in
                ResultRow(result)
            }

            if isLoading {
                // Dyskretny wskaźnik ładowania
                ProgressView()
                    .scaleEffect(0.5)
            }
        }
        .task(id: query) {
            isLoading = true
            results = await search(query)
            isLoading = false
        }
    }
}

2. Głęboka integracja z macOS

Raycast czuje się jak naturalny element macOS. Respektuje konwencje platformy, jednocześnie przesuwając granice.

Natywne zachowania: - Vibrancy i rozmycie (NSVisualEffectView) - Systemowe kolory akcentów - SF Symbols wszędzie - Natywne skróty klawiszowe - Respektuje systemowy tryb ciemny/jasny

Implementacja:

// Vibrancy macOS
struct RaycastWindow: View {
    var body: some View {
        VStack {
            // Zawartość
        }
        .background(.ultraThinMaterial)  // Natywne rozmycie
        .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
    }
}

// Systemowy kolor akcentu
Text("Zaznaczone")
    .foregroundStyle(.tint)  // Podąża za preferencjami systemu

// SF Symbols z kolorami semantycznymi
Image(systemName: "checkmark.circle.fill")
    .foregroundStyle(.green)

Projekt okna:

┌────────────────────────────────────────────────────────────┐
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░                                                      ░░ │
│ ░░  > Szukaj w Raycast...                               ░░ │
│ ░░                                                      ░░ │
│ ░░  ┌────────────────────────────────────────────────┐  ░░ │
│ ░░  │ [A]  Aplikacje                         Cmd+1   │  ░░ │
│ ░░  │ [F]  Wyszukiwanie plików               Cmd+2   │  ░░ │
│ ░░  │ [C]  Historia schowka                  Cmd+3   │  ░░ │
│ ░░  │ [S]  Polecenia systemowe               Cmd+4   │  ░░ │
│ ░░  └────────────────────────────────────────────────┘  ░░ │
│ ░░                                                      ░░ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
└────────────────────────────────────────────────────────────┘
                    ↑ Vibrancy pokazuje pulpit przez okno

3. Doskonałość klawiatury z wizualnym feedbackiem

Każda akcja ma skrót klawiszowy. Każdy skrót ma wizualne potwierdzenie.

Wzorce wyświetlania skrótów:

LISTA WYNIKÓW:
┌────────────────────────────────────────────────────────────┐
│ Ostatnie aplikacje                                         │
│                                                            │
│  (*)  Visual Studio Code                         Cmd+1     │
│       ~/Projects/my-app                                    │
│                                                            │
│  ( )  Figma                                      Cmd+2     │
│       ~/Design/project.fig                                 │
│                                                            │
│  ( )  Terminal                                   Cmd+3     │
│                                                            │
│ ───────────────────────────────────────────────────────────│
│  Akcje                           Cmd+K  lub  ->  aby zobaczyć │
└────────────────────────────────────────────────────────────┘

Panel akcji:

┌────────────────────────────────────────────────────────────┐
│ Akcje dla "Visual Studio Code"                    esc <-   │
│                                                            │
│  Otwórz                                           Enter    │
│  Otwórz w nowym oknie                         Cmd+Enter    │
│  ──────────────────────────────────────────────────────── │
│  Pokaż w Finderze                           Cmd+Shift+F    │
│  Kopiuj ścieżkę                             Cmd+Shift+C    │
│  ──────────────────────────────────────────────────────── │
│  Przenieś do Kosza                       Cmd+Backspace     │
└────────────────────────────────────────────────────────────┘

Zasady projektowania: - Pokazuj skróty inline (nie w tooltipach) - Grupuj skróty według klawisza modyfikującego - Używaj standardowych skrótów macOS gdzie oczekiwane - Wzorce mnemoniczne (⌘⇧F = Finder)


4. Osobowość w szczegółach

Raycast jest wydajny, ale nie sterylny. Małe przyjemności czynią go niezapomnianym.

Przykłady: - Konfetti przy osiągnięciach - Subtelne dźwięki dla akcji - Zabawne puste stany - Easter eggi dla zaawansowanych użytkowników

Przykład pustego stanu:

┌────────────────────────────────────────────────────────────┐
│                                                            │
│                        [?]                                 │
│                                                            │
│               Brak wyników dla "asdfgh"                    │
│                                                            │
│          Spróbuj wyszukać coś innego,                      │
│          lub sprawdź Sklep z rozszerzeniami                │
│                                                            │
│              [Przeglądaj rozszerzenia]                     │
│                                                            │
└────────────────────────────────────────────────────────────┘

Wzorzec konfetti (dla osiągnięć):

// Po ukończeniu onboardingu, zdobyciu odznaki itp.
ConfettiView()
    .transition(.opacity)
    .animation(.spring(), value: showConfetti)

Wytyczne dotyczące osobowości: - Radość nie powinna opóźniać - Świętowania są zasłużone (nie losowe) - Osobowość w tekstach („Brak wyników dla…" vs „Błąd: 0 wyników") - Dźwięk jest opcjonalny i dyskretny


5. Projektowanie ekosystemu rozszerzeń

Sklep z rozszerzeniami Raycast jest pięknie zaprojektowany.

Karta rozszerzenia:

┌────────────────────────────────────────────────────────────┐
│  ┌──────┐                                                  │
│  │ [+]  │  GitHub                                          │
│  │      │  Szukaj repozytoriów, PR-ów i issues             │
│  └──────┘                                                  │
│           ★★★★★  1.2k ocen  •  Od Raycast                  │
│                                                            │
│           [Zainstaluj]                                     │
└────────────────────────────────────────────────────────────┘

Widok szczegółów rozszerzenia:

┌────────────────────────────────────────────────────────────┐
│                                                            │
│  ← GitHub                                      [Odinstaluj]│
│                                                            │
│  ┌──────────────────────────────────────────────────────┐ │
│  │                                                       │ │
│  │        [Zrzut ekranu rozszerzenia w użyciu]          │ │
│  │                                                       │ │
│  └──────────────────────────────────────────────────────┘ │
│                                                            │
│  Polecenia                                                 │
│  ├─ Szukaj repozytoriów                                    │
│  ├─ Moje Pull Requesty                                     │
│  ├─ Szukaj Issues                                          │
│  └─ Utwórz Issue                                           │
│                                                            │
│  Historia zmian                                            │
│  ├─ v2.1.0  Dodano filtrowanie organizacji                 │
│  └─ v2.0.0  Kompletne przepisanie z nowym API              │
│                                                            │
└────────────────────────────────────────────────────────────┘

6. Projektowanie formularzy dla rozszerzeń

Rozszerzenia używają spójnego systemu formularzy.

Wzorce formularzy:

┌────────────────────────────────────────────────────────────┐
│ Utwórz GitHub Issue                                   esc  │
│                                                            │
│  Repozytorium                                              │
│  ┌──────────────────────────────────────────────────────┐ │
│  │ raycast/extensions                               ▾   │ │
│  └──────────────────────────────────────────────────────┘ │
│                                                            │
│  Tytuł                                                     │
│  ┌──────────────────────────────────────────────────────┐ │
│  │ Bug: Wyszukiwanie nie działa                         │ │
│  └──────────────────────────────────────────────────────┘ │
│                                                            │
│  Opis                                                      │
│  ┌──────────────────────────────────────────────────────┐ │
│  │ Kiedy szukam...                                      │ │
│  │                                                       │ │
│  │                                                       │ │
│  └──────────────────────────────────────────────────────┘ │
│                                                            │
│  Etykiety                                  (opcjonalne)    │
│  ┌──────────────────────────────────────────────────────┐ │
│  │ [bug] [needs-triage]                             +   │ │
│  └──────────────────────────────────────────────────────┘ │
│                                                            │
│           [Utwórz Issue ⌘↵]    [Anuluj esc]                │
│                                                            │
└────────────────────────────────────────────────────────────┘

Stylowanie komponentów formularza:

// Formularz w stylu Raycast w SwiftUI
Form {
    Section("Repozytorium") {
        Picker("", selection: $repo) {
            ForEach(repos) { repo in
                Text(repo.name).tag(repo)
            }
        }
        .labelsHidden()
    }

    Section("Tytuł") {
        TextField("Bug: ...", text: $title)
    }

    Section("Opis") {
        TextEditor(text: $description)
            .frame(minHeight: 100)
    }
}
.formStyle(.grouped)

Wzorce projektowe do nauki

Wzorzec „najpierw wyszukiwanie"

Wszystko zaczyna się od wyszukiwania.

┌────────────────────────────────────────────────────────────┐
│  > |                                                       │
│    ^ Kursor tutaj natychmiast po uruchomieniu              │
│                                                            │
│  Wpisz, aby szukać poleceń, aplikacji, plików i więcej     │
└────────────────────────────────────────────────────────────┘

Implementacja: - Auto-focus na wyszukiwarce przy uruchomieniu - Wyszukiwanie rozmyte z podświetleniem - Inteligentne rankingowanie (ostatnie + częste + trafne) - Wyniki pojawiają się podczas pisania

Nawigacja hierarchiczna

Głęboka funkcjonalność bez złożoności.

Poziom 1: Główne wyszukiwanie
    ↓ Enter
Poziom 2: Polecenia rozszerzenia
    ↓ Enter
Poziom 3: Wyniki akcji
    ↓ ⌘K
Poziom 4: Panel akcji

Zawsze: esc cofa o jeden poziom

Potwierdzenia HUD

Szybki feedback bez przerywania.

Po akcji:
┌────────────────────────┐
│  ✓ Skopiowano do schowka│
└────────────────────────┘
    ↑ Pojawia się na chwilę, zanika
      Nie wymaga zamknięcia
// Wzorzec HUD
struct HUDView: View {
    @State private var show = false

    var body: some View {
        if show {
            Text("✓ Skopiowano")
                .padding(.horizontal, 16)
                .padding(.vertical, 8)
                .background(.regularMaterial)
                .cornerRadius(8)
                .transition(.opacity.combined(with: .scale))
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                        withAnimation { show = false }
                    }
                }
        }
    }
}

Co warto zapożyczyć od Raycast

Dla interfejsu launchera/wyszukiwarki

  1. 50ms albo nic - Szybkość jest niepodlegająca dyskusji
  2. Skróty klawiszowe wszędzie - Odkrywalne, nie ukryte
  3. Wyszukiwanie rozmyte z podświetleniem - Pokaż dlaczego wyniki pasują
  4. Panele akcji (⌘K) - Akcje kontekstowe
  5. Potwierdzenia HUD - Szybki, nieblokujący feedback

Dla aplikacji macOS

  1. Natywny vibrancy - Używaj NSVisualEffectView/.material
  2. SF Symbols - Spójne z systemem
  3. Kolory systemowe - Respektuj preferencje koloru akcentu
  4. Ciągłe zaokrąglenia - .cornerRadius(style: .continuous)
  5. Standardowe skróty - Nie wymyślaj na nowo ⌘C, ⌘V, itp.

Konkretne techniki

Technika Jak zastosować
Natychmiastowe uruchomienie Wstępnie rozgrzej okno, leniwie ładuj zawartość
Wyszukiwanie rozmyte Użyj Fuse.js lub podobnej biblioteki
Podpowiedzi skrótów Inline ⌘1, ⌘2, itp. w wynikach
Panel akcji Drugorzędne akcje na ⌘K lub →
Feedback HUD W stylu toast, auto-znikanie
API rozszerzeń Jasne kontrakty, spójne UI

System kolorów

Raycast używa semantycznych kolorów powiązanych z systemem.

// Semantyczne kolory inspirowane Raycast
extension Color {
    static let rayBackground = Color(nsColor: .windowBackgroundColor)
    static let raySecondary = Color.secondary
    static let rayTertiary = Color(nsColor: .tertiaryLabelColor)

    // Kolory statusów
    static let raySuccess = Color.green
    static let rayWarning = Color.orange
    static let rayError = Color.red

    // Akcent podąża za systemem
    static let rayAccent = Color.accentColor
}

// Przykład trybu ciemnego
struct ResultRow: View {
    var body: some View {
        HStack {
            Image(systemName: "app.fill")
                .foregroundStyle(.secondary)

            Text("Nazwa aplikacji")
                .foregroundStyle(.primary)

            Spacer()

            Text("⌘ 1")
                .font(.caption)
                .foregroundStyle(.tertiary)
                .padding(.horizontal, 6)
                .padding(.vertical, 2)
                .background(Color.secondary.opacity(0.2))
                .cornerRadius(4)
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 8)
    }
}

Kluczowe spostrzeżenia

„Każda milisekunda ma znaczenie. Użytkownicy czują różnicę między 50ms a 100ms."

„Skróty klawiszowe powinny być odkrywalne, nie zapamiętywane z dokumentacji."

„Osobowość sprawia, że oprogramowanie jest pamiętane. Wydajność sprawia, że jest niezastąpione."

„Rozszerzenia powinny czuć się natywnie, nie przyklejone. Ten sam język UI, te same interakcje."


Często zadawane pytania

Jak Raycast osiąga czasy odpowiedzi poniżej 50ms?

Raycast używa natywnej implementacji w Swift (nie Electron), wstępnie ładuje indeks rozszerzeń przy uruchomieniu, agresywnie cache'uje wyniki wyszukiwania i optymalizuje potok renderowania. UI pojawia się natychmiast, podczas gdy dane ładują się asynchronicznie. Próg 50ms jest traktowany jako twarde ograniczenie, nie cel.

Dlaczego Raycast wygląda tak natywnie dla macOS?

Raycast używa NSVisualEffectView dla vibrancy/rozmycia, SF Symbols dla wszystkich ikon, systemowych kolorów akcentów respektujących preferencje użytkownika i standardowych skrótów klawiszowych macOS. Okno ma ciągły promień zaokrąglenia i automatycznie podąża za systemowym trybem ciemnym/jasnym.

Jakie jest podejście Raycast do skrótów klawiszowych?

Każda akcja ma skrót klawiszowy wyświetlany inline w wynikach (⌘1, ⌘2, itp.), nie ukryty w tooltipach. Skróty używają wzorców mnemonicznych (⌘⇧F dla Findera, ⌘⇧C dla Kopiuj ścieżkę). Panel akcji (⌘K) zapewnia drugorzędne akcje z własnymi skrótami.

Jak Raycast dodaje osobowość bez spowalniania?

Świętowania są zasłużone i krótkie — konfetti pojawia się po osiągnięciach, nie losowo. Puste stany mają zabawne teksty („Brak wyników dla…" zamiast „Błąd: 0 wyników"). Dźwięki są opcjonalne i dyskretne. Radość nigdy nie blokuje użytkownika ani nie dodaje opóźnień.

Jak system rozszerzeń Raycast utrzymuje spójność?

Wszystkie rozszerzenia używają tych samych komponentów UI (formularze, listy, panele akcji), podążają za tymi samymi wzorcami nawigacji (Enter żeby zagłębić się, Escape żeby wrócić) i dziedziczą stylowanie systemowe. API wymusza spójność, więc rozszerzenia zewnętrzne są nie do odróżnienia od wbudowanych funkcji.


Zasoby