# Separator

## Кратко

`Separator` — примитив визуального разделения для горизонтальных и вертикальных границ между соседними областями интерфейса.

## Импорты

```tsx
import { Separator, type SeparatorProps } from '@vega-ui/react';
```

## Экспортируемые типы

- `SeparatorProps`

## Базовое использование

```tsx
import { Heading, Separator, Text } from '@vega-ui/react';

export default function BasicUsage() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
        <Heading as='h3' size={4}>Notifications</Heading>
        <Text size={2} style={{ color: 'var(--label-secondary)' }}>Email me when someone mentions me or assigns me a task.</Text>
      </div>
      <Separator />
      <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
        <Heading as='h3' size={4}>Privacy</Heading>
        <Text size={2} style={{ color: 'var(--label-secondary)' }}>Only workspace members can see my profile and activity.</Text>
      </div>
    </div>
  );
}
```

## Варианты

- Ориентация: `horizontal` или `vertical`
- Раскладка: горизонтальная линия на всю ширину или вертикальный разделитель на всю высоту

## Пограничные случаи

- Вертикальным разделителям нужна явная высота от родительской раскладки.
- Компонент визуальный; отступы по-прежнему определяет окружающая раскладка.
- Декоративные разделители не должны заменять настоящие заголовки или группировку через fieldset.

## Частые ошибки

- Вертикальный разделитель рендерится без заданной высоты родителя.
- Разделители используются там, где на самом деле нужна семантическая группировка.
- От `Separator` ожидают, что он сам создаст отступы в раскладке.

## Примеры

### Базовый

#### Базовый: разделитель в сводке заказа

Горизонтальный `Separator` растягивается на ширину контейнера и отделяет итог от позиций заказа. Отступы задаёт `gap` родителя, а не сам разделитель.

```tsx
import { Separator, Text } from '@vega-ui/react';

const items = [
  { name: 'Wireless keyboard', price: '$89.00' },
  { name: 'USB-C hub, 7 ports', price: '$45.00' },
  { name: 'Shipping', price: '$6.50' },
];

export default function HorizontalSectionDivider() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%' }}>
      {items.map((item) => (
        <div key={item.name} style={{ display: 'flex', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
          <Text size={2} style={{ color: 'var(--label-secondary)' }}>{item.name}</Text>
          <Text size={2} style={{ fontVariantNumeric: 'tabular-nums' }}>{item.price}</Text>
        </div>
      ))}
      <Separator />
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
        <Text size={3} fontWeight={500}>Total</Text>
        <Text size={3} fontWeight={500} style={{ fontVariantNumeric: 'tabular-nums' }}>$140.50</Text>
      </div>
    </div>
  );
}
```

### Контролируемый/с состоянием

#### Контролируемый/с состоянием: разделители только между видимыми группами

Разделители рендерятся между группами и никогда перед первой, поэтому скрытая фильтром группа не оставляет двойной или висящей линии. Каждая группа остаётся `section` с подписью из заголовка, так что структура не держится на линии.

```tsx
import { Button, Heading, Separator, Text } from '@vega-ui/react';
import { Fragment, useState } from 'react';

const projects = [
  { name: 'Website redesign', status: 'active' },
  { name: 'Mobile onboarding', status: 'active' },
  { name: 'Q3 pricing test', status: 'paused' },
  { name: 'Legacy API migration', status: 'archived' },
  { name: 'Holiday campaign', status: 'archived' },
];

const groups = [
  { status: 'active', title: 'Active' },
  { status: 'paused', title: 'Paused' },
  { status: 'archived', title: 'Archived' },
];

export default function ConditionalDivider() {
  const [showArchived, setShowArchived] = useState(false);

  const visibleGroups = [];

  for (const group of groups) {
    if (group.status === 'archived' && !showArchived) continue;

    const items = projects.filter((project) => project.status === group.status);
    if (items.length === 0) continue;

    visibleGroups.push({ ...group, items });
  }

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
      <Button
        size='sm'
        variant='secondary'
        appearance='ghost'
        aria-pressed={showArchived}
        onClick={() => setShowArchived(!showArchived)}
        style={{ justifySelf: 'start' }}
      >
        Show archived
      </Button>
      {visibleGroups.map((group, index) => (
        <Fragment key={group.status}>
          {index > 0 && <Separator />}
          <section aria-labelledby={`projects-${group.status}`} style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
            <Heading as='h3' size={2} id={`projects-${group.status}`} style={{ color: 'var(--label-secondary)' }}>{group.title}</Heading>
            {group.items.map((project) => (
              <Text key={project.name} size={3}>{project.name}</Text>
            ))}
          </section>
        </Fragment>
      ))}
    </div>
  );
}
```

### Форма/интеграция

#### Форма/интеграция: разделитель между группами полей

Две группы `Fieldset` со своими легендами, разделённые линией. Разделитель только визуальный: доступные имена группам дают легенды, а отправка читает обе группы через `FormData`.

```tsx
import {
  Button,
  Fieldset,
  FieldsetLegend,
  Label,
  Separator,
  Text,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';
import { useState, type FormEvent } from 'react';

export default function DividerBetweenFieldGroups() {
  const [saved, setSaved] = useState('');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    setSaved(`Saved ${data.get('fullName')} <${data.get('email')}>, ${data.get('company')}`);
  };

  return (
    <form
      onSubmit={onSubmit}
      onReset={() => setSaved('')}
      style={{ display: 'grid', gap: 'var(--spacing-12)', width: '100%' }}
    >
      <Fieldset style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-8)' }}>Personal details</FieldsetLegend>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='billing-name'>Full name</Label>
          <TextField>
            <TextFieldInput id='billing-name' name='fullName' autoComplete='name' required />
          </TextField>
        </div>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='billing-email'>Email</Label>
          <TextField>
            <TextFieldInput id='billing-email' name='email' type='email' autoComplete='email' required />
          </TextField>
        </div>
      </Fieldset>
      <Separator />
      <Fieldset style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-8)' }}>Company</FieldsetLegend>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='billing-company'>Company name</Label>
          <TextField>
            <TextFieldInput id='billing-company' name='company' autoComplete='organization' />
          </TextField>
        </div>
      </Fieldset>
      <div style={{ display: 'grid', gap: 'var(--spacing-6)' }}>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--spacing-4)' }}>
          <Button type='reset' variant='secondary' appearance='ghost'>Reset</Button>
          <Button type='submit'>Save</Button>
        </div>
        {saved && <Text size={2} role='status' style={{ color: 'var(--label-secondary)', overflowWrap: 'anywhere' }}>{saved}</Text>}
      </div>
    </form>
  );
}
```

### Раскладка/оверлей

#### Раскладка/оверлей: вертикальные разделители на панели инструментов

Вертикальные разделители делят `role='toolbar'` на группы форматирования, списков и выравнивания. Вертикальный разделитель берёт `height: 100%`, поэтому у панели задана явная высота; у каждой кнопки-иконки свой `aria-label`.

```tsx
import { Icon, IconButton, Separator } from '@vega-ui/react';
import { AlignCenter, AlignLeft, Bold, Italic, Link, List, ListOrdered } from '@vega-ui/icons';

export default function VerticalDividerInToolbar() {
  return (
    <div
      role='toolbar'
      aria-label='Text formatting'
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: 'var(--spacing-2)',
        padding: 'var(--spacing-2)',
        border: '1px solid var(--separator-opaque)',
        borderRadius: 'var(--radius-3)',
        background: 'var(--surface-primary)',
      }}
    >
      <IconButton aria-label='Bold' variant='secondary' appearance='transparent'>
        <Icon><Bold /></Icon>
      </IconButton>
      <IconButton aria-label='Italic' variant='secondary' appearance='transparent'>
        <Icon><Italic /></Icon>
      </IconButton>
      <IconButton aria-label='Insert link' variant='secondary' appearance='transparent'>
        <Icon><Link /></Icon>
      </IconButton>
      <Separator orientation='vertical' style={{ height: 'var(--spacing-10)', marginInline: 'var(--spacing-2)' }} />
      <IconButton aria-label='Bulleted list' variant='secondary' appearance='transparent'>
        <Icon><List /></Icon>
      </IconButton>
      <IconButton aria-label='Numbered list' variant='secondary' appearance='transparent'>
        <Icon><ListOrdered /></Icon>
      </IconButton>
      <Separator orientation='vertical' style={{ height: 'var(--spacing-10)', marginInline: 'var(--spacing-2)' }} />
      <IconButton aria-label='Align left' variant='secondary' appearance='transparent'>
        <Icon><AlignLeft /></Icon>
      </IconButton>
      <IconButton aria-label='Align center' variant='secondary' appearance='transparent'>
        <Icon><AlignCenter /></Icon>
      </IconButton>
    </div>
  );
}
```

### Ошибка

#### Ошибка: разделитель перед опасной зоной

Разделитель отделяет форму с разрушительным действием от обычных настроек. Подтверждение проверяется при отправке: при несовпадении поле помечается невалидным, `HelperText` связывается через `aria-describedby`, а ввод снимает ошибку.

```tsx
import {
  Button,
  Heading,
  HelperText,
  Label,
  Separator,
  Text,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';
import { useState, type CSSProperties, type FormEvent } from 'react';

const workspaceName = 'acme-marketing';

const destructive = {
  '--button-background-color': 'var(--color-red-accent-500)',
  '--button-background-color-hover': 'var(--color-red-accent-600)',
  '--button-background-color-active': 'var(--color-red-accent-700)',
} as CSSProperties;

export default function DividerBeforeDestructiveSection() {
  const [error, setError] = useState('');
  const [deleted, setDeleted] = useState(false);

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);

    if (data.get('confirmation') !== workspaceName) {
      setError(`Type ${workspaceName} exactly to confirm.`);
      return;
    }

    setDeleted(true);
  };

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-12)', width: '100%' }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
        <Heading as='h3' size={4}>Workspace name</Heading>
        <Text size={2} style={{ color: 'var(--label-secondary)' }}>{workspaceName}</Text>
      </div>
      <Separator />
      <form onSubmit={onSubmit} aria-labelledby='danger-zone-title' style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
          <Heading as='h3' size={4} id='danger-zone-title'>Delete workspace</Heading>
          <Text size={2} style={{ color: 'var(--label-secondary)' }}>All projects, files and billing history are removed permanently.</Text>
        </div>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='delete-confirmation'>Type {workspaceName} to confirm</Label>
          <TextField error={error !== ''}>
            <TextFieldInput
              id='delete-confirmation'
              name='confirmation'
              autoComplete='off'
              aria-invalid={error !== ''}
              aria-describedby={error ? 'delete-confirmation-error' : undefined}
              onChange={() => setError('')}
            />
          </TextField>
          {error && <HelperText id='delete-confirmation-error' error>{error}</HelperText>}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-6)', flexWrap: 'wrap' }}>
          <Button type='submit' style={destructive}>Delete workspace</Button>
          {deleted && <Text size={2} role='status' style={{ color: 'var(--label-secondary)' }}>Workspace scheduled for deletion.</Text>}
        </div>
      </form>
    </div>
  );
}
```

### Отключённый

#### Отключённый: разделитель в отключённом Fieldset

У `Separator` нет отключённого состояния: он лежит внутри `Fieldset`, который отключён целиком, а пояснение связано с группой через `aria-describedby`. Переключатель показывает одну и ту же раскладку в обоих состояниях.

```tsx
import {
  Button,
  Fieldset,
  FieldsetLegend,
  Label,
  Separator,
  Text,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';
import { useState } from 'react';

export default function MutedSectionBreakInDisabledPanel() {
  const [locked, setLocked] = useState(true);

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
      <Fieldset disabled={locked} aria-describedby='smtp-lock-note' style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-8)' }}>Outgoing mail</FieldsetLegend>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='smtp-host'>SMTP host</Label>
          <TextField>
            <TextFieldInput id='smtp-host' name='host' defaultValue='smtp.acme.io' />
          </TextField>
        </div>
        <Separator />
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='smtp-port'>Port</Label>
          <TextField>
            <TextFieldInput id='smtp-port' name='port' inputMode='numeric' defaultValue='587' />
          </TextField>
        </div>
      </Fieldset>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
        <Text id='smtp-lock-note' size={2} style={{ color: 'var(--label-secondary)' }}>
          {locked ? 'Managed by your IT team.' : 'Changes apply to new messages only.'}
        </Text>
        <Button
          size='sm'
          variant='secondary'
          appearance='ghost'
          aria-pressed={!locked}
          onClick={() => setLocked(!locked)}
          style={{ flexShrink: 0 }}
        >
          {locked ? 'Unlock settings' : 'Lock settings'}
        </Button>
      </div>
    </div>
  );
}
```

### Пограничный случай

#### Пограничный случай: вертикальный разделитель без фиксированной высоты

`height: 100%` схлопывается в ноль, если у родителя нет определённой высоты. В строке, высота которой задаётся содержимым, переопределите её через `height: 'auto'` и `alignSelf: 'stretch'`, чтобы линия заняла высоту строки.

```tsx
import { Separator, Text } from '@vega-ui/react';

const stats = [
  { label: 'Revenue', value: '$12.4k' },
  { label: 'Orders', value: '318' },
  { label: 'Refunds', value: '4' },
];

export default function ConstrainedVerticalDivider() {
  return (
    <div style={{ display: 'flex', gap: 'var(--spacing-10)' }}>
      {stats.map((stat, index) => (
        <div key={stat.label} style={{ display: 'flex', gap: 'var(--spacing-10)' }}>
          {index > 0 && <Separator orientation='vertical' style={{ height: 'auto', alignSelf: 'stretch' }} />}
          <div style={{ display: 'grid', gap: 'var(--spacing-1)' }}>
            <Text size={2} style={{ color: 'var(--label-secondary)' }}>{stat.label}</Text>
            <Text size={5} fontWeight={500} style={{ fontVariantNumeric: 'tabular-nums' }}>{stat.value}</Text>
          </div>
        </div>
      ))}
    </div>
  );
}
```

#### Пограничный случай: декоративный разделитель

Внутри списка каждый `role='separator'` озвучивается между пунктами. Если линия чисто декоративная, передайте `aria-hidden`, чтобы скринридер читал только пункты списка.

```tsx
import { Separator, Text } from '@vega-ui/react';

const posts = [
  { title: 'Release notes 3.2', date: 'Mar 12' },
  { title: 'Dark mode for charts', date: 'Mar 5' },
  { title: 'Faster CSV exports', date: 'Feb 26' },
];

export default function DecorativeDivider() {
  return (
    <ul style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%', margin: 0, padding: 0, listStyle: 'none' }}>
      {posts.map((post, index) => (
        <li key={post.title} style={{ display: 'grid', gap: 'var(--spacing-6)' }}>
          {index > 0 && <Separator aria-hidden />}
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 'var(--spacing-8)' }}>
            <Text size={2}>{post.title}</Text>
            <Text size={2} style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums', flexShrink: 0 }}>{post.date}</Text>
          </div>
        </li>
      ))}
    </ul>
  );
}
```

## Анатомия

### Обзор

`Separator` — примитив из одной части, который рендерит тонкую разделительную линию с `role='separator'`.

### Обязательные части

#### `Separator`

Обязательная часть. Отвечает и за семантическую роль разделителя, и за визуальную линию.

### Необязательные части

Экспортируемых дочерних частей нет.

### Порядок композиции

1. `Separator`

### Допустимые варианты композиции

Горизонтальный разделитель между содержимым и подвалом и вертикальный — между действиями подвала. У строки подвала явная высота, потому что вертикальный разделитель берёт `height: 100%` от родителя.

```tsx
import { Button, Card, Heading, Separator, Text } from '@vega-ui/react';

export default function ValidCompositionPatterns() {
  return (
    <Card style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
        <Heading as='h3' size={4}>Quarterly report</Heading>
        <Text size={2} style={{ color: 'var(--label-secondary)' }}>Revenue grew 12% compared to the previous quarter.</Text>
      </div>
      <Separator />
      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)', height: 'var(--spacing-16)' }}>
        <Button size='sm' variant='secondary' appearance='ghost'>Download PDF</Button>
        <Separator orientation='vertical' />
        <Button size='sm' variant='secondary' appearance='ghost'>Share</Button>
      </div>
    </Card>
  );
}
```

### Недопустимые варианты композиции

#### Вертикальный разделитель без высоты родителя

Разделитель схлопывается, потому что у него нет контекста высоты.

#### Разделитель как единственная структура раздела

Это создаёт визуальное разделение без семантической подписи.
