Skip to content
31 changes: 19 additions & 12 deletions docs/components-api/button.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@

> ✅ **Реализован**: `ExtraButtonComponent` (`@cdek-it/angular-ui-kit`) соответствует спецификации.

| Свойство | Описание | Типизация |
| --------------- | --------------------------------------------- | -------------------------------------------------- |
| `label` | текст на кнопке | `string` |
| `icon` | класс иконки tabler icon на кнопке | `string` |
| `iconPosition` | позиция иконки относительно текста | `left \| right` |
| `variant` | вариант отображения кнопки | `primary \| secondary \| tertiary \| text \| link` |
| `severity` | семантическое состояние кнопки | `base \| danger \| warning \| success \| info` |
| `size` | размер кнопки | `small \| base \| large \| xlarge` |
| `rounded` | скруглённая форма кнопки | `boolean` |
| `fluid` | растягивает кнопку на всю ширину контейнера | `boolean` |
| `disabled` | отключённое состояние кнопки | `boolean` |
| `loading` | состояние загрузки: спиннер, кнопка неактивна | `boolean` |
| Свойство | Описание | Типизация |
| --------------- | ------------------------------------------------ | ----------------------------------------------------------------------- |
| `label` | текст на кнопке | `string` |
| `icon` | класс иконки tabler icon на кнопке | `string` |
| `iconPosition` | позиция иконки относительно текста | `left \| right` |
| `variant` | вариант отображения кнопки | `primary \| secondary \| tertiary \| outlined \| text \| link` |
| `severity` | семантическое состояние кнопки | `base \| danger \| warning \| success \| info` |
| `size` | размер кнопки | `small \| base \| large \| xlarge` |
| `rounded` | скруглённая форма кнопки | `boolean` |
| `fluid` | растягивает кнопку на всю ширину контейнера | `boolean` |
| `disabled` | отключённое состояние кнопки | `boolean` |
| `loading` | состояние загрузки: спиннер, кнопка неактивна | `boolean` |
| `iconOnly` | режим «только иконка» без текста | `boolean` |
| `showBadge` | показывать бейдж поверх кнопки | `boolean` |
| `badge` | текст бейджа (виден только при `showBadge=true`) | `string` |
| `badgeSeverity` | цветовая схема бейджа | `success \| info \| warning \| danger \| secondary \| contrast \| null` |
| `ariaLabel` | метка для программ экранного доступа | `string \| undefined` |
| `autofocus` | автофокус при монтировании компонента | `boolean` |
| `tabindex` | порядок перехода по Tab | `number \| undefined` |
48 changes: 35 additions & 13 deletions src/lib/components/button/button.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { Button, ButtonSeverity as PrimeButtonSeverity } from 'primeng/button';

export type ExtraButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'text' | 'link';
export type ExtraButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'outlined' | 'text' | 'link';
export type ExtraButtonSeverity = 'base' | 'danger' | 'warning' | 'success' | 'info';
export type ExtraButtonSize = 'small' | 'base' | 'large' | 'xlarge';
export type ExtraButtonIconPosition = 'left' | 'right';
export type ExtraButtonBadgeSeverity = 'success' | 'info' | 'warning' | 'danger' | 'secondary' | 'contrast' | null;
type PrimeBadgeSeverity = Extract<Button['badgeSeverity'], string | null>;
type ExtraButtonSeverityValue = PrimeButtonSeverity;
type ExtraButtonBadgeSeverityValue = PrimeBadgeSeverity;

@Component({
selector: 'extra-button',
Expand All @@ -13,34 +17,47 @@ export type ExtraButtonIconPosition = 'left' | 'right';
imports: [Button],
template: `
<p-button
[label]="label"
[icon]="icon"
[iconPos]="iconPosition"
[severity]="primeSeverity"
[label]="iconOnly ? '' : label"
[disabled]="disabled"
[loading]="loading"
[size]="primeSize"
[styleClass]="primeStyleClass"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

чем не устроил геттер?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AxyIX вернуть геттер primeStyleClass в кнопке a81d7ce

[rounded]="rounded"
[fluid]="fluid"
[outlined]="variant === 'outlined'"
[text]="variant === 'text'"
[link]="variant === 'link'"
[disabled]="disabled"
[loading]="loading"
[icon]="icon"
[iconPos]="iconPosition"
[severity]="primeSeverity"
[badge]="showBadge ? badge || ' ' : undefined"
[badgeSeverity]="primeBadgeSeverity"
[fluid]="fluid"
[ariaLabel]="ariaLabel"
[autofocus]="autofocus"
[tabindex]="tabindex"
(onFocus)="focus.emit($event)"
(onBlur)="blur.emit($event)"
></p-button>
`
})
export class ExtraButtonComponent {
@Input() label = '';
@Input() icon = '';
@Input() iconPosition: ExtraButtonIconPosition = 'left';
@Input() variant: ExtraButtonVariant = 'primary';
@Input() severity: ExtraButtonSeverity = 'base';
@Input() size: ExtraButtonSize = 'base';
@Input() rounded = false;
@Input() fluid = false;
@Input() iconPosition: ExtraButtonIconPosition = 'left';
@Input() iconOnly = false;
@Input() icon = '';
@Input() disabled = false;
@Input() loading = false;
@Input() badge = '';
@Input() badgeSeverity: ExtraButtonBadgeSeverity = null;
@Input() showBadge = false;
@Input() fluid = false;
@Input() ariaLabel: string | undefined = undefined;
@Input() autofocus = false;
@Input() tabindex: number | undefined = undefined;

@Output() focus = new EventEmitter<FocusEvent>();
@Output() blur = new EventEmitter<FocusEvent>();
Expand All @@ -55,12 +72,17 @@ export class ExtraButtonComponent {
return this.size === 'xlarge' ? 'p-button-xlg' : '';
}

get primeSeverity(): PrimeButtonSeverity {
get primeSeverity(): ExtraButtonSeverityValue | null {
if (this.severity === 'base') {
if (this.variant === 'secondary') return 'secondary';
if (this.variant === 'tertiary') return 'contrast';
if (this.variant === 'tertiary' || this.variant === 'outlined') return 'contrast';
return null;
}
return this.severity === 'warning' ? 'warn' : this.severity;
}

get primeBadgeSeverity(): ExtraButtonBadgeSeverityValue {
if (this.badgeSeverity === 'warning') return 'warn';
return this.badgeSeverity;
}
}
67 changes: 55 additions & 12 deletions src/lib/components/button/button.figma.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ updated: '2026-06-22'

| Свойство | Тип | По умолчанию | Описание |
|----------|-----|--------------|---------|
| `label` | `string` | `'Button'` | Текст кнопки |
| `variant` | `'primary' \| 'secondary' \| 'outlined' \| 'text' \| 'link'` | `'primary'` | Вариант отображения — соответствует Figma-свойству `variant` |
| `severity` | `'success' \| 'warning' \| 'danger' \| 'info' \| null` | `null` | Семантический акцент — соответствует Figma-компоненту `<Button.Danger>` и аналогам |
| `label` | `string` | `''` | Текст кнопки |
| `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'outlined' \| 'text' \| 'link'` | `'primary'` | Вариант отображения — `primary`/`secondary`/`tertiary`/`text`/`link` соответствуют Figma-свойству `variant` на `<Button.Base>` (nodeId `160:5223`), `outlined` — Figma-свойству `variant=outlined` на `<Button.Danger>`/`<Button.Warning>`/`<Button.Success>`/`<Button.Info>` (nodeId `162:5457` и аналоги) |
| `severity` | `'base' \| 'danger' \| 'warning' \| 'success' \| 'info'` | `'base'` | Семантический акцент — соответствует Figma-компоненту `<Button.Danger>` и аналогам |
| `size` | `'small' \| 'base' \| 'large' \| 'xlarge'` | `'base'` | Размер кнопки — соответствует Figma-свойству `size` |
| `icon` | `string` | `''` | CSS-класс иконки (например `'pi pi-check'`); доступные иконки — [icons.md](../../figma-code-connect/icons.md) |
| `iconPos` | `'prefix' \| 'postfix' \| null` | `null` | Позиция иконки — соответствует Figma-свойству `icon-position` |
| `iconPosition` | `'left' \| 'right'` | `'left'` | Позиция иконки — соответствует Figma-свойству `icon-position` |
| `iconOnly` | `boolean` | `false` | Режим «только иконка» без текста — соответствует Figma-свойству `icon-only` |
| `disabled` | `boolean` | `false` | Отключённое состояние — соответствует Figma-состоянию `state=disabled` |
| `loading` | `boolean` | `false` | Состояние загрузки со спиннером — соответствует Figma-состоянию `state=loading` |
Expand All @@ -37,11 +37,43 @@ updated: '2026-06-22'
| `showBadge` | `boolean` | `false` | Показывать бейдж поверх кнопки |
| `badge` | `string` | `''` | Текст бейджа (отображается только при `showBadge=true`) |
| `badgeSeverity` | `'success' \| 'info' \| 'warning' \| 'danger' \| 'secondary' \| 'contrast' \| null` | `null` | Цветовая схема бейджа; цвета соответствуют токенам [tokens.md](../../figma-code-connect/tokens.md) |
| `text` | `boolean` | `false` | Текстовый вариант без фона и рамки (алиас для `variant='text'`) |
| `ariaLabel` | `string \| undefined` | `undefined` | Метка для программ экранного доступа |
| `autofocus` | `boolean` | `false` | Автофокус при монтировании компонента |
| `tabindex` | `number \| undefined` | `undefined` | Порядок перехода по Tab |

Отдельного `text` нет — текстовый стиль задаётся только через `variant="text"`; дублирующий булев
проп был в черновике ветки, убран как нарушающий правило «одно имя — один смысл»
(`docs/naming-rules.md`).

### `tertiary` vs `outlined` — два разных Figma-компонента, два разных кода-значения

В Figma `<Button.Base>` и `<Button.Danger>`/`<Button.Warning>`/`<Button.Success>`/`<Button.Info>` —
РАЗНЫЕ компонент-сеты с разными наборами значений `variant`: у Base — `primary | secondary |
tertiary | text | link`, у severity-семейства — `basic | outlined | text` (подтверждено
скриншотами `<Button.Base>` nodeId `160:5223` и `<Button.Danger>` nodeId `162:5457`). Визуально это
два разных вида:

- `<Button.Base>` variant=tertiary — сплошная тёмная заливка без рамки (`severity=contrast` у
PrimeNG, `outlined=false`).
- `<Button.Danger>`/аналоги variant=outlined — кнопка с цветной рамкой и прозрачным фоном
(`outlined=true`).

Раньше оба вида смешивались в один код-уровневый `variant="tertiary"`, который неявно превращался
в контурный при `severity !== 'base'`. Это было неочевидно и не давало собрать контурную кнопку
нейтрального цвета. Теперь `outlined` — самостоятельное значение `variant`
(`[outlined]="variant === 'outlined'"`, без зависимости от `severity`): `tertiary` всегда даёт
тёмную заливку, `outlined` всегда даёт рамку, цвет рамки/текста в обоих случаях задаётся отдельно
через `severity`.

> ⚠️ **`variant="outlined"` + `severity="base"` не имеет референса в Figma и визуально не
> работает.** В Figma `outlined` существует только у `<Button.Danger>`/`<Button.Warning>`/
> `<Button.Success>`/`<Button.Info>` — у `<Button.Base>` такого состояния нет. На уровне кода
> комбинация технически доступна (оба пропа независимы), но при `severity="base"` она маппится на
> PrimeNG `severity="contrast"`, чей цветовой токен — белый текст, рассчитанный на тёмную заливку
> (`#2b2e33`); при `outlined=true` заливка снимается, и текст остаётся белым на прозрачном фоне —
> нечитаемо на светлой странице. Использовать `outlined` только вместе с `severity !== 'base'`
> (`danger`/`warning`/`success`/`info`).

## Variants

### Primary / Base (основная кнопка)
Expand All @@ -54,18 +86,29 @@ Figma: `<Button.Base>`, variant=primary, size=base, state=default — nodeId `16

### Secondary / Base (вторичная кнопка)

Figma: `<Button.Base>`, variant=tertiary (в коде: `variant="secondary"`), size=base, state=default
Figma: `<Button.Base>`, variant=secondary, size=base, state=default

```html
<extra-button variant="secondary" label="Отмена"></extra-button>
```

### Outlined / Large (контурная кнопка, большой размер)
### Tertiary / Large (тёмная заливка, большой размер)

Figma: `<Button.Base>`, variant=tertiary, size=large, state=default — сплошная тёмная заливка,
не зависит от `severity` (см. раздел «`tertiary` vs `outlined`» выше)

```html
<extra-button variant="tertiary" size="large" label="Подробнее"></extra-button>
```

### Outlined (контурная кнопка)

Figma: `<Button.Base>`, variant=outlined, size=large, state=default
Figma: `<Button.Danger>`/`<Button.Warning>`/`<Button.Success>`/`<Button.Info>`, variant=outlined —
nodeId `162:5457` и аналоги. Только с `severity` отличной от `base` — см. предупреждение выше.

```html
<extra-button variant="outlined" size="large" label="Подробнее"></extra-button>
<extra-button variant="outlined" severity="danger" label="Удалить"></extra-button>
<extra-button variant="outlined" severity="success" label="Подтвердить"></extra-button>
```

### Text / Base (текстовая кнопка)
Expand All @@ -84,15 +127,15 @@ Figma: `<Button.Base>`, variant=link, size=base, state=default
<extra-button variant="link" label="Перейти"></extra-button>
```

### С иконкой слева (prefix)
### С иконкой слева (left)

Figma: `<Button.Base>`, icon-position=prefix, icon-only=false

```html
<extra-button
label="Добавить"
icon="pi pi-plus"
iconPos="prefix"
iconPosition="left"
></extra-button>
```

Expand Down Expand Up @@ -164,7 +207,7 @@ Figma: `<Button.Base>`, rounded=true
## Do / Don't

**Do:**
- Используйте `variant="text"` для текстовых кнопок; `[text]="true"` оставлен только как алиас для совместимости с PrimeNG.
- Используйте `variant="text"` для текстовых кнопок — отдельного булева `text` нет, это единственный способ
- Используйте `variant="primary"` для единственного главного действия на экране
- Для деструктивных действий задавайте `severity="danger"` и добавляйте иконку через [icons.md](../../figma-code-connect/icons.md)
- При кнопке только с иконкой всегда задавайте `ariaLabel` для доступности
Expand Down
Loading
Loading