Перейти к основному содержанию

Макеты

Этот документ охватывает компоненты макетов уровня 3 в архитектуре @ui8kit/core: DashLayout, LayoutBlock и SplitBlock. Это компоненты уровня шаблонов (организмы), которые организуют UI-компоненты уровня 2 и примитивы уровня 1 в структурные макеты страниц. Эта страница фокусируется на паттернах композиции макетов, системах хуков контента и особых соображениях при создании структур приложений. Для базовых примитивов макетов (Grid, Flex, Stack) см. Базовые компоненты.
Для UI-компонентов, используемых внутри макетов, см. UI-компоненты.
Для деталей API и справки по пропсам см. API компонентов макетов.

Обзор архитектуры макетов

Система макетов работает на самом высоком архитектурном уровне, компонуя UI-компоненты и примитивы в полные структуры страниц. Три компонента макетов служат различным структурным целям:
Внешние зависимости

Уровень 1: Используемые примитивы

Уровень 2: Используемые UI-компоненты

Уровень 3: Компоненты макетов

использует

использует

использует

использует

использует

использует

требует

использует

использует

использует

использует

использует

использует

использует

использует

использует

использует

использует

использует

использует

опциональные иконки

опциональные иконки

DashLayout
(Структура дашборда)

LayoutBlock
(Секции контента)

SplitBlock
(Двухколоночные макеты)

Card

Button

Text

Title

Badge

Icon

Image

Container

Group

Block
(Семантические секции)

Box
(Общий контейнер)

Grid
(CSS Grid)

Stack
(Вертикальный/горизонтальный)

react-resizable-panels
(PanelGroup, Panel)

lucide-react
(Компоненты иконок)
Источники: src/layouts/DashLayout.tsx1-99 src/layouts/LayoutBlock.tsx1-389 src/layouts/SplitBlock.tsx1-145 README.md147-168

DashLayout - Шаблон дашборда

DashLayout предоставляет полную структуру дашборда с изменяемыми панелями, навигационным заголовком и сворачиваемой боковой панелью. Он использует react-resizable-panels для интерактивного управления панелями.

Структура компонента

рендерит

рендерит

содержит

содержит

содержит

оборачивает

оборачивает

использует

использует

использует

использует

использует

использует

использует

использует

Dashboard
(Основной компонент)

Navbar
(Заголовок с брендом + переключатель темы)

Sidebar
(Сворачиваемая навигация)

Main Panel
(Область контента)

PanelGroup
(direction: horizontal)

Panel
(Контейнер боковой панели)

PanelResizeHandle
(Ручка перетаскивания)

Panel
(Контейнер контента)

Block component='nav'

Group

Button variant='ghost'

Icon

Text

Block component='aside'

Stack

Container

Ключевые интерфейсы и пропсы

ИнтерфейсНазначениеКлючевые пропсы
DashboardPropsКонфигурация основного макетаpage, children, sidebar, navbarProps
NavbarPropsКонфигурация заголовкаisDarkMode, toggleDarkMode, brand
SidebarPropsКонфигурация боковой панелиchildren, title, dataClass

Детали реализации

Компонент Dashboard в src/layouts/DashLayout.tsx64-91 организует макет:
  1. Navbar рендерится вверху с переключателем темы в src/layouts/DashLayout.tsx34-49
  2. PanelGroup с direction="horizontal" создает изменяемый макет в src/layouts/DashLayout.tsx75-88
  3. Panel компоненты определяют боковую панель (20% по умолчанию, диапазон 10-40%) и область контента (80% по умолчанию, минимум 50%) в src/layouts/DashLayout.tsx76-86
  4. PanelResizeHandle обеспечивает интерактивное изменение размера в src/layouts/DashLayout.tsx80
  5. Container оборачивает основной контент с адаптивным размером в src/layouts/DashLayout.tsx84

Паттерн использования

import { DashLayout } from '@ui8kit/core';

<DashLayout
  sidebar={<NavSidebar />}
  navbarProps={{
    brand: "MyApp",
    isDarkMode: true,
    toggleDarkMode: handleToggle
  }}
>
  <Container>
    <Card p="lg">Content</Card>
  </Container>
</DashLayout>
Макет автоматически сохраняет размеры панелей через autoSaveId="dashlayout-panels" в src/layouts/DashLayout.tsx75 Источники: src/layouts/DashLayout.tsx1-99 README.md155-168

LayoutBlock - Гибкие секции контента

LayoutBlock - это универсальный компонент макета, который рендерит секции контента с тремя режимами макета (grid, flex, stack) и мощной системой хуков контента для динамического рендеринга.

Режимы макета и конфигурация

рендерит

рендерит

рендерит

использует

использует

использует

оборачивает с

оборачивает в

LayoutBlock

Режим Grid
(layout='grid')

Режим Flex
(layout='flex')

Режим Stack
(layout='stack')

Компонент Grid
cols, gap, align, justify

Компонент Group
gap, align, justify, wrap

Компонент Stack
gap, align

Container
(опциональная обертка)

Block component='section'

Система хуков контента

Система хуков контента в src/layouts/LayoutBlock.tsx22-30 позволяет заменить любую часть рендерящегося контента:
ХукНазначениеСигнатура
beforeHeaderКонтент перед секцией заголовка(content: any) => ReactNode
headerКастомный рендерер заголовка(content: any) => ReactNode
afterHeaderКонтент после секции заголовка(content: any) => ReactNode
beforeItemsКонтент перед списком элементов(content: any) => ReactNode
itemКастомный рендерер элемента(item: any, index: number) => ReactNode
afterItemsКонтент после списка элементов(content: any) => ReactNode

Рендереры по умолчанию

Компонент предоставляет рендереры по умолчанию в src/layouts/LayoutBlock.tsx123-227:

Пропсы конфигурации

Категория пропсовПропсыНазначение
Управление макетомlayout, useContainer, containerSizeРежим и настройки контейнера
Настройки Gridcols, gridCols, gap, align, justifyКонфигурация CSS Grid
Настройки Flexwrap, flexWrapПоведение переноса Flexbox
Настройки StackstackAlignВертикальное выравнивание
Настройки заголовкаshowHeader, headerAlignОтображение и выравнивание заголовка
Данные контентаcontent, content.itemsСтруктура данных для рендеринга
КастомизацияcontentHooksКастомные функции рендеринга

Структура данных контента

Пропс content в src/layouts/LayoutBlock.tsx61-77 следует этой схеме:
content: {
  badge?: string;          // Опциональный текст бейджа
  title?: string;          // Заголовок секции
  description?: string;    // Описание секции
  items?: Array<{
    id: string;           // Обязательный уникальный идентификатор
    title?: string;       // Заголовок элемента
    description: string;  // Описание элемента (обязательно)
    image?: {             // Опциональное изображение
      src: string;
      alt: string;
    };
    lucideIcon?: any;     // Опциональный компонент иконки Lucide
    [key: string]: any;   // Дополнительные кастомные данные
  }>;
}

Рендеринг режимов макета

Логика рендеринга в src/layouts/LayoutBlock.tsx298-353 выбирает соответствующий компонент макета:
grid

flex

stack

renderItems()

тип layout?

Компонент Grid
cols, gap, align, justify
data-class='layout-grid'

Компонент Group
gap, align, justify, wrap
data-class='layout-flex'

Компонент Stack
gap, align
data-class='layout-stack'

itemRenderer(item, index)
Каждый макет оборачивает элементы в соответствующий примитивный компонент с атрибутами data-class для таргетинга DOM в src/layouts/LayoutBlock.tsx320-348

Примеры использования

Grid с карточками:
<LayoutBlock
  layout="grid"
  cols="1-2-3"
  gap="lg"
  content={{
    badge: "Features",
    title: "Why Choose Us",
    description: "Discover our advantages",
    items: [
      {
        id: "1",
        title: "Fast",
        description: "Lightning speed",
        lucideIcon: Zap
      }
    ]
  }}
/>
Flex с кастомными хуками:
<LayoutBlock
  layout="flex"
  wrap="wrap"
  contentHooks={{
    header: (content) => <CustomHeader {...content} />,
    item: (item, index) => <CustomCard key={item.id} {...item} />
  }}
  content={data}
/>
Источники: src/layouts/LayoutBlock.tsx1-389 README.md149-154

SplitBlock - Двухколоночный разделенный макет

SplitBlock создает двухколоночные макеты с гибкими медиа и контентными секциями, обычно используемые для героических секций, демонстрации функций и комбинаций контента/изображений.

Режимы макета

SplitBlock

Режим Container
(splitSection=false)

Режим на всю ширину
(splitSection=true)

Block component='section'

Обертка Container

Grid cols='1-2'

Grid cols='1-2'
data-class='split-grid'

leftMedia=true

leftMedia=false

Система хуков контента

Подобно LayoutBlock, SplitBlock поддерживает хуки контента в src/layouts/SplitBlock.tsx11-15:
ХукНазначениеСигнатура
beforeContentКонтент перед основной секцией(content: any) => ReactNode
contentРендерер основного контента(content: any) => ReactNode
afterContentКонтент после основной секции(content: any) => ReactNode

API слотов

API слотов в src/layouts/SplitBlock.tsx36-42 предоставляет именованные переопределения:
slots?: {
  media?: ReactNode;      // Переопределить медиа секцию
  header?: ReactNode;     // Будущее: Слот заголовка
  body?: ReactNode;       // Будущее: Слот тела
  actions?: ReactNode;    // Будущее: Слот действий
}
Разрешение медиа-слота в src/layouts/SplitBlock.tsx91 приоритизирует переопределения слотов над прямыми пропсами.

Пропсы конфигурации

ПропсТипПо умолчаниюНазначение
mediaSectionReactNode-Контент для медиа-колонки
contentSectionReactNode-Контент для контентной колонки
leftMediabooleanfalseПозиционировать медиа слева
splitSectionbooleantrueИспользовать grid на всю ширину vs контейнер
containerSizeContainerSizingProps["size"]"lg"Размер контейнера (когда splitSection=false)
gapVariantGridProps["gap"]"lg"Промежуток между колонками
alignVariantGridProps["align"]"center"Вертикальное выравнивание

Логика рендеринга макета

Логика рендеринга в src/layouts/SplitBlock.tsx94-136 создает два различных макета: Режим Container (splitSection=false) в src/layouts/SplitBlock.tsx96-111:
  • Оборачивает grid в адаптивный Container
  • Применяет пропсы containerSize и padding
  • Подходит для стандартных секций страниц
Режим на всю ширину (splitSection=true) в src/layouts/SplitBlock.tsx115-135:
  • Grid непосредственно после Block без контейнера
  • Использует data-class="split-grid" для идентификации в src/layouts/SplitBlock.tsx129
  • Применяет flex-1 items-center для высоты на весь viewport

Порядок колонок

Порядок колонок определяется пропсом leftMedia в src/layouts/SplitBlock.tsx106-107 и src/layouts/SplitBlock.tsx131-132:
  • leftMedia=true: [mediaSection, contentSection]
  • leftMedia=false: [contentSection, mediaSection]

Примеры использования

Базовый Split с медиа:
<SplitBlock
  mediaSection={
    <Image src="/hero.jpg" alt="Hero" fit="cover" />
  }
  contentSection={
    <Stack gap="lg">
      <Title order={1}>Welcome</Title>
      <Text>Description text</Text>
      <Button>Get Started</Button>
    </Stack>
  }
  leftMedia
/>
С хуками контента:
<SplitBlock
  content={{ title: "Feature", description: "Details" }}
  contentHooks={{
    beforeContent: (content) => <Badge>{content.badge}</Badge>,
    content: (content) => (
      <Stack gap="md">
        <Title>{content.title}</Title>
        <Text>{content.description}</Text>
      </Stack>
    ),
    afterContent: () => <Button>Learn More</Button>
  }}
  slots={{
    media: <Image src="/feature.jpg" />
  }}
/>
Hero на всю ширину:
<SplitBlock
  splitSection
  leftMedia={false}
  mediaSection={<HeroImage />}
  contentSection={<HeroContent />}
/>
Источники: src/layouts/SplitBlock.tsx1-145 README.md149-154

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

Компоненты макетов активно используют примитивы уровня 1 для структуры. Понимание этих примитивов важно для работы с макетами.

Компонент Grid

Используется LayoutBlock (режим grid) и SplitBlock для макетов CSS Grid:
применяет

применяет

применяет

применяет

'1-2-3' создает

Grid
(CSS Grid)

вариант cols
'1-2-3', '2', '3', '4'

вариант gap
'sm', 'md', 'lg', 'xl'

вариант align
'start', 'center', 'end'

вариант justify
'start', 'center', 'between'

Адаптивные колонки
1 колонка мобильная
2 колонки планшет
3 колонки десктоп
Общие конфигурации Grid:
  • cols="1-2-3" - Адаптивные 1/2/3 колонки
  • cols="2" - Фиксированные 2 колонки
  • gap="lg" - Большой промежуток между элементами
  • align="center" - Центрировать элементы вертикально

Компонент Stack

Используется LayoutBlock (режим stack) и внутри контентных секций для вертикальных макетов:
ПропсЗначенияНазначение
gap"sm", "md", "lg", "xl", "2xl", "3xl"Вертикальный интервал
align"start", "center", "end"Горизонтальное выравнивание
ta"left", "center", "right"Выравнивание текста

Компонент Block

Все компоненты макетов используют Block как корневой семантический контейнер:
<Block
  component="section"  // Семантический HTML5 тег
  w="full"            // Полная ширина
  py="xl"             // Вертикальный padding
  data-class="layout-block"  // Идентификация DOM
>
  {/* Контент макета */}
</Block>
Пропс component в src/layouts/DashLayout.tsx16 src/layouts/DashLayout.tsx36 и src/layouts/DashLayout.tsx74 обеспечивает семантическую HTML структуру (<section>, <nav>, <aside>, <main>). Источники: src/layouts/LayoutBlock.tsx3-16 src/layouts/SplitBlock.tsx3-8 src/layouts/DashLayout.tsx2

Паттерны композиции

Компоненты макетов следуют специфическим паттернам композиции для построения сложных структур.

Паттерн 1: Вложенная композиция макетов

Макеты могут быть вложены для создания сложных структур страниц:
children

children

children

рендерит

рендерит

рендерит

DashLayout
(Оболочка приложения)

LayoutBlock layout='grid'
(Секция Hero)

LayoutBlock layout='flex'
(Секция Features)

SplitBlock
(Секция контента)

Card

Card

Card
Пример:
<DashLayout sidebar={<Navigation />}>
  <LayoutBlock layout="grid" cols="1-2-3" content={heroData} />
  <SplitBlock mediaSection={<Image />} contentSection={<Content />} />
  <LayoutBlock layout="stack" content={testimonialsData} />
</DashLayout>

Паттерн 2: Управление контейнером

Макеты используют компонент Container для адаптивного контроля ширины:
МакетИспользование ContainerКогда используется
DashLayoutВсегда использует Container в src/layouts/DashLayout.tsx84Оборачивает основную панель контента
LayoutBlockОпционально через пропс useContainer в src/layouts/LayoutBlock.tsx259По умолчанию true, можно отключить для полной ширины
SplitBlockУсловно через пропс splitSection в src/layouts/SplitBlock.tsx94-112Используется когда splitSection=false

Паттерн 3: Переопределение хука контента

Система хуков контента обеспечивает прогрессивное улучшение:
переопределяет

fallback

Рендерер по умолчанию
(Встроенный)

Кастомный хук
(Предоставленный пользователем)

Финальный рендер
(Вывод)

Данные контента
Цепочка fallback:
  1. Проверить наличие кастомного contentHooks.item в src/layouts/LayoutBlock.tsx302
  2. Откатиться к рендереру по умолчанию в src/layouts/LayoutBlock.tsx285
  3. Применить умолчания специфичные для макета в src/layouts/LayoutBlock.tsx230-254

Паттерн 4: Переопределения на основе слотов

Слоты SplitBlock предоставляют целенаправленную кастомизацию без полных хуков контента:
<SplitBlock
  // Базовые пропсы
  leftMedia
  gap="xl"
  
  // Оригинальные секции
  mediaSection={<DefaultImage />}
  contentSection={<DefaultContent />}
  
  // Переопределения слотов
  slots={{
    media: <CustomVideo />  // Переопределяет mediaSection
  }}
/>
Разрешение в src/layouts/SplitBlock.tsx91 приоритизирует слоты над прямыми пропсами.

Паттерн 5: Рендеринг на основе данных

Макеты принимают структурированные данные и рендерят автоматически:
const pageData = {
  badge: "New",
  title: "Feature Section",
  description: "Amazing features",
  items: [
    { id: "1", title: "Fast", description: "Speed", lucideIcon: Zap },
    { id: "2", title: "Secure", description: "Safe", lucideIcon: Lock }
  ]
};

<LayoutBlock layout="grid" cols="2" content={pageData} />
Конвейер рендеринга в src/layouts/LayoutBlock.tsx288-361 обрабатывает:
  1. Рендеринг заголовка из content.badge/title/description
  2. Итерацию и рендеринг элементов из content.items
  3. Обертку макета на основе режима layout
Источники: src/layouts/LayoutBlock.tsx256-386 src/layouts/SplitBlock.tsx67-143 src/layouts/DashLayout.tsx64-91

Особые соображения

Семантическая HTML структура

Компоненты макетов обеспечивают семантическую HTML5 структуру:
КомпонентКорневой элементНазначение
DashLayout<nav> + <main>Навигационный заголовок + основной контент в src/layouts/DashLayout.tsx36-74
DashLayout.Sidebar<aside>Боковая навигация в src/layouts/DashLayout.tsx16
LayoutBlock<section>Контентные секции в src/layouts/LayoutBlock.tsx366
SplitBlock<section>Разделенные контентные секции в src/layouts/SplitBlock.tsx97-117
Это обеспечивает доступность и SEO соответствие без дополнительной конфигурации.

Атрибуты Data-Class

Все макеты применяют атрибуты data-class для консистентного таргетинга DOM:
КомпонентЗначение Data-ClassРасположение
DashLayout.Navbardata-role="dash-navbar"src/layouts/DashLayout.tsx36
DashLayout.SidebarКастомный через пропсsrc/layouts/DashLayout.tsx16
LayoutBlockdata-class="layout-block"src/layouts/LayoutBlock.tsx371
LayoutBlock Griddata-class="layout-grid"src/layouts/LayoutBlock.tsx320
LayoutBlock Flexdata-class="layout-flex"src/layouts/LayoutBlock.tsx333
LayoutBlock Stackdata-class="layout-stack"src/layouts/LayoutBlock.tsx344
SplitBlock Griddata-class="split-grid"src/layouts/SplitBlock.tsx129
Это обеспечивает надежное тестирование и стилизацию без зависимости от className.

Адаптивное поведение

Макеты обрабатывают адаптивный дизайн через пропсы вариантов: Адаптивные колонки Grid:
  • cols="1-2-3" создает брейкпоинты мобильная→планшет→десктоп
  • Автоматически сворачивается в 1 колонку на мобильных
  • Масштабируется до 2 колонок на планшете (md:)
  • Расширяется до 3 колонок на десктопе (lg:)
Адаптивный размер Container:
  • containerSize="lg" применяет max-w-7xl с корректировками брейкпоинтов
  • Автоматически добавляет горизонтальный padding на мобильных
  • Центрирует контент с mx="auto"
Изменение размера панелей (DashLayout):

Соображения производительности

Управление ключами элементов: Компонент LayoutBlock требует уникальный id в элементах контента в src/layouts/LayoutBlock.tsx66 для эффективной reconciliation React. Ключи применяются в src/layouts/LayoutBlock.tsx306 Возможности мемоизации: Хуки и рендереры контента оцениваются при каждом рендере. Для дорогих вычислений оберните в useMemo:
const contentHooks = useMemo(() => ({
  item: (item, index) => <ExpensiveComponent {...item} />
}), [dependencies]);
Сохранение состояния панели: DashLayout автоматически сохраняет размеры панелей в localStorage через react-resizable-panels, уменьшая сдвиг макета при перезагрузке.

Внешние зависимости

КомпонентЗависимостьНазначениеУстановка
DashLayoutreact-resizable-panelsСистема изменяемых панелейnpm install react-resizable-panels
Все макетыlucide-reactОпциональные компоненты иконокnpm install lucide-react
Они перечислены как peer зависимости и должны быть установлены отдельно при использовании макетов. Источники: src/layouts/DashLayout.tsx4 src/layouts/LayoutBlock.tsx366-383 src/layouts/SplitBlock.tsx94-136

Примеры использования

Приложение дашборд

Полный дашборд с навигацией, изменяемыми панелями и контентными секциями:
import { DashLayout, LayoutBlock, Card } from '@ui8kit/core';
import { Home, Settings, Users } from 'lucide-react';

function DashboardApp() {
  const [darkMode, setDarkMode] = useState(false);
  
  return (
    <DashLayout
      navbarProps={{
        brand: "Admin Panel",
        isDarkMode: darkMode,
        toggleDarkMode: () => setDarkMode(!darkMode)
      }}
      sidebar={
        <Stack gap="md">
          <Button variant="ghost" leftSection={<Home />}>
            Dashboard
          </Button>
          <Button variant="ghost" leftSection={<Users />}>
            Users
          </Button>
          <Button variant="ghost" leftSection={<Settings />}>
            Settings
          </Button>
        </Stack>
      }
    >
      <LayoutBlock
        layout="grid"
        cols="1-2-3"
        gap="lg"
        content={{
          title: "Overview",
          description: "Key metrics",
          items: statsData
        }}
      />
    </DashLayout>
  );
}

Маркетинговая целевая страница

Hero секция с разделенным макетом и сеткой функций:
import { SplitBlock, LayoutBlock } from '@ui8kit/core';
import { Zap, Shield, Globe } from 'lucide-react';

function LandingPage() {
  return (
    <>
      <SplitBlock
        leftMedia={false}
        splitSection
        mediaSection={
          <Image
            src="/hero.jpg"
            alt="Hero"
            fit="cover"
            h="full"
          />
        }
        contentSection={
          <Stack gap="xl" align="start" p="2xl">
            <Badge variant="secondary">New Release</Badge>
            <Title order={1} size="4xl" fw="bold">
              Build Faster
            </Title>
            <Text size="xl" c="secondary-foreground">
              Create amazing interfaces with minimal code
            </Text>
            <Group gap="md">
              <Button variant="primary" size="lg">
                Get Started
              </Button>
              <Button variant="outline" size="lg">
                Learn More
              </Button>
            </Group>
          </Stack>
        }
      />
      
      <LayoutBlock
        layout="grid"
        cols="1-2-3"
        gap="xl"
        py="3xl"
        content={{
          badge: "Features",
          title: "Why Choose Us",
          description: "Everything you need to succeed",
          items: [
            {
              id: "fast",
              title: "Lightning Fast",
              description: "Optimized for performance",
              lucideIcon: Zap
            },
            {
              id: "secure",
              title: "Secure by Default",
              description: "Built with security in mind",
              lucideIcon: Shield
            },
            {
              id: "global",
              title: "Global Scale",
              description: "Deploy worldwide instantly",
              lucideIcon: Globe
            }
          ]
        }}
      />
    </>
  );
}

Макет статьи с насыщенным контентом

Макет Stack с кастомными хуками контента для богатого контента:
import { LayoutBlock, createLayoutContentHook } from '@ui8kit/core';

const articleHooks = createLayoutContentHook({
  header: (content) => (
    <Stack gap="md" align="center" ta="center">
      <Badge>{content.category}</Badge>
      <Title order={1} size="4xl">{content.title}</Title>
      <Text c="muted" size="sm">
        {content.author} • {content.date}
      </Text>
    </Stack>
  ),
  item: (item, index) => (
    <Card p="xl" rounded="lg" shadow="sm">
      <Stack gap="lg">
        <Title order={2} size="2xl">{item.title}</Title>
        <Text>{item.content}</Text>
        {item.quote && (
          <Box p="lg" bg="muted" rounded="md" border="1px solid border">
            <Text size="lg" fw="medium">"{item.quote}"</Text>
          </Box>
        )}
      </Stack>
    </Card>
  )
});

function ArticlePage() {
  return (
    <LayoutBlock
      layout="stack"
      gap="2xl"
      containerSize="md"
      contentHooks={articleHooks}
      content={articleData}
    />
  );
}
Источники: README.md36-168 src/layouts/DashLayout.tsx53-96 src/layouts/LayoutBlock.tsx256-389 src/layouts/SplitBlock.tsx67-143