Создание собственных компонентов на базе Skeleton

Библиотека Skeleton предоставляет готовую инфраструктуру для построения UI-компонентов в приложениях на SvelteKit. Центральная концепция — легковесные и реактивные компоненты, которые можно расширять и настраивать под конкретные задачи. Skeleton построена с использованием Svelte store и реактивных деклараций, что позволяет создавать динамические интерфейсы без избыточного кода.

В основе компонентов лежат обёртки (Wrapper) и слоты (<slot>), которые позволяют разделять логику и представление. Любой пользовательский компонент Skeleton может наследовать базовые стили и функциональность, при этом добавляя собственные свойства и методы.

Создание базового компонента

Для создания собственного компонента важно определить:

  1. Пропсы — параметры, которые компонент принимает извне.
  2. Слоты — места, куда можно вставлять дочерние элементы.
  3. Реактивные состояния — значения, от которых зависит визуальное отображение.

Пример структуры компонента:

<script lang="ts">
  import { writable } from 'svelte/store';
  export let title: string = 'Заголовок';
  export let active: boolean = false;

  const count = writable(0);

  function increment() {
    count.update(n => n + 1);
  }
</script>

<div class="my-component {active ? 'active' : ''}">
  <h2>{title}</h2>
  <slot></slot>
  <button on:click={increment}>Увеличить: {$count}</button>
</div>

<style>
  .my-component {
    padding: 1rem;
    border: 1px solid #ccc;
    border-radius: 0.5rem;
    transition: all 0.2s ease;
  }
  .my-component.active {
    border-color: #4f46e5;
    background-color: #eef2ff;
  }
</style>

В этом примере реализуется компонент с реактивным счётчиком, поддержкой слотов и условным классом, который меняет визуальное оформление в зависимости от пропса active.

Использование Skeleton utilities для стилизации

Skeleton предоставляет ряд утилитных классов и функций, которые ускоряют разработку:

  • class: — динамическое применение классов.
  • use: — подключение пользовательских действий (actions) к DOM-элементам.
  • theme — доступ к переменным темы через Svelte store.

Пример применения темы:

<script lang="ts">
  import { theme } from '@skeleton/ui';
</script>

<div class="card" style="background-color: {theme.background}">
  <slot></slot>
</div>

Такой подход обеспечивает гибкость и согласованность дизайна, позволяя автоматически подстраивать компоненты под текущую тему приложения.

Композиция и переиспользование компонентов

Skeleton поощряет композицию компонентов вместо наследования. Основные приёмы:

  1. Вложенные компоненты — использование слотов и пропсов для передачи контента и конфигурации.
  2. Объединение действий (actions) — подключение нескольких use: действий к одному элементу.
  3. Декларативные состояния — использование writable и derived для управления состоянием без классов и методов.

Пример вложенной структуры:

<MyCard title="Профиль">
  <UserAvatar user={user}/>
  <UserInfo user={user}/>
</MyCard>

MyCard становится контейнером, который контролирует визуальное оформление и реактивность дочерних компонентов, а дочерние компоненты остаются изолированными по логике.

Динамическая генерация компонентов

Skeleton позволяет создавать динамические компоненты на основе данных с помощью Svelte {#each} и реактивных выражений:

<script lang="ts">
  import { Button } from '@skeleton/ui';
  let buttons = ['Сохранить', 'Отменить', 'Удалить'];
</script>

<div class="button-group">
  {#each buttons as btn}
    <Button on:click={() => console.log(btn)}>{btn}</Button>
  {/each}
</div>

Это позволяет создавать массово настраиваемые интерфейсы, где набор компонентов определяется данными, а не статической разметкой.

Расширение существующих компонентов Skeleton

Для глубокой кастомизации часто используется композиция с обёрткой и переопределение пропсов. Например, расширение стандартного Button:

<script lang="ts">
  import { Button } from '@skeleton/ui';
  export let loading: boolean = false;
</script>

<Button class:loading={loading}>
  <slot></slot>
  {#if loading}
    <span class="spinner"></span>
  {/if}
</Button>

<style>
  .spinner {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    border: 2px solid transparent;
    border-top-color: #4f46e5;
    border-radius: 50%;
    animation: spin 1s linear infinite;
  }

  @keyframes spin {
    to { transform: rotate(360deg); }
  }
</style>

Такой компонент сохраняет все возможности исходного Button, добавляя новую функциональность: индикатор загрузки без нарушения существующего API.

Рекомендации по организации компонентов

  • Малые и независимые — каждый компонент должен решать одну задачу.
  • Слоты для гибкости — не фиксировать внутреннюю структуру, предоставлять возможность вставки контента.
  • Реактивные пропсы — использовать Svelte store для динамического обновления состояния.
  • Общая тема и стили — подключать Skeleton theme для согласованности интерфейса.
  • Композиция вместо наследования — минимизирует связность и увеличивает переиспользуемость.

Этот подход позволяет создавать многоразовые, гибкие и легко поддерживаемые UI-компоненты, полностью интегрированные в экосистему SvelteKit и Skeleton.