Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/components-api/selectbutton.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
| `optionLabel` | наименование поля, содержащего отображаемое значение | `string` |
| `multiple` | множественный выбор | `boolean` |
| `size` | размер кнопок | `sm \| base \| lg \| xlg` |
| `fluid` | растягивает группу на всю ширину контейнера | `boolean` |

# События

Expand Down
9 changes: 9 additions & 0 deletions src/lib/components/select-button/select-button.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export type ExtraSelectButtonSize = 'sm' | 'base' | 'lg' | 'xlg';
selector: 'extra-select-button',
standalone: true,
imports: [SelectButton, SharedModule, FormsModule],
// В обычном режиме хост остаётся inline (группа встаёт в строку рядом с другими элементами).
// В режиме fluid делаем его блоком: сам p-selectbutton — inline-flex, и без блочного хоста
// его width: 100% считается не от контейнера-родителя.
host: {
'[style.display]': "fluid ? 'block' : null"
},
template: `
<!-- Ступень xlg навешивается классом на корень: styleClass в PrimeNG 20 на корне
p-selectbutton не остаётся, а раздаётся вниз каждому p-togglebutton, и стили
Expand All @@ -37,6 +43,7 @@ export type ExtraSelectButtonSize = 'sm' | 'base' | 'lg' | 'xlg';
[allowEmpty]="allowEmpty"
[disabled]="isDisabled"
[size]="primeSize"
[fluid]="fluid"
[class.p-selectbutton-xlarge]="size === 'xlg'"
>
<ng-template pTemplate="item" let-item>
Expand All @@ -56,6 +63,8 @@ export class ExtraSelectButtonComponent implements ControlValueAccessor {
@Input() size: ExtraSelectButtonSize = 'base';
@Input() multiple = false;
@Input() allowEmpty = true;
/** Растягивает группу на всю доступную ширину контейнера, сегменты делят её поровну. */
@Input() fluid = false;

@Output() onChange = new EventEmitter<ExtraSelectButtonChangeEvent>();

Expand Down
14 changes: 14 additions & 0 deletions src/lib/components/select-button/select-button.figma.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ updated: '2026-06-22'
| `size` | `'sm' \| 'base' \| 'lg' \| 'xlg'` | `'base'` | Размер контрола |
| `multiple` | `boolean` | `false` | Множественный выбор: модель — массив значений вместо одиночного значения |
| `allowEmpty` | `boolean` | `true` | Разрешает снять выбор (пустое значение) повторным нажатием на активный сегмент |
| `fluid` | `boolean` | `false` | Растягивает группу на всю доступную ширину контейнера; сегменты делят её поровну |
| `disabled` | `boolean` | `false` | Отключённое состояние всего контрола — соответствует Figma-свойству `state=disabled`; задаётся через `[disabled]` или `setDisabledState` формы |

Выбранное значение задаётся не отдельным инпутом, а моделью через `ControlValueAccessor`: используйте `[(ngModel)]`, `formControl` или `formControlName`. В одиночном режиме модель — `string`, в режиме `multiple` — `string[]`. Изменение значения также доступно через `@Output() valueChange`.
Expand Down Expand Up @@ -114,6 +115,18 @@ Figma: `<SelectButton>`, state=default — один сегмент с `disabled:
></extra-select-button>
```

### Во всю ширину контейнера (fluid)

Figma: `<SelectButton>`, state=default — группа растянута по ширине родителя

```html
<extra-select-button
[options]="viewOptions"
[fluid]="true"
[(ngModel)]="selectedView"
></extra-select-button>
```

## Slots

Не используются. Содержимое сегментов задаётся через `@Input() options`: подпись берётся из поля `optionLabel`, иконка — из поля `icon` объекта опции.
Expand All @@ -135,6 +148,7 @@ Figma: `<SelectButton>`, state=default — один сегмент с `disabled:
- Для режима «выбор нескольких» задавайте `[multiple]="true"` — модель станет массивом
- Используйте `[allowEmpty]="false"`, когда хотя бы один сегмент должен оставаться активным
- Для иконок в сегментах задавайте поле `icon` в объекте опции — классы берите из [icons.md](../../figma-code-connect/icons.md)
- Используйте `[fluid]="true"` в формах на всю ширину и мобильных макетах

**Don't:**
- Не используйте для навигации по разделам — для этого предназначен компонент Tabs
Expand Down
16 changes: 12 additions & 4 deletions src/stories/components/dialog/examples/dialog-basic.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core';
import { StoryObj } from '@storybook/angular';
import { ExtraButtonComponent } from '../../../../lib/components/button/button.component';
import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component';
import { ExtraDialogComponent, ExtraDialogSize } from '../../../../lib/components/dialog/dialog.component';
import { ExtraDialogTemplateDirective } from '../../../../lib/components/dialog/dialog-template.directive';

const template = `
Expand All @@ -10,6 +10,7 @@ const template = `

<extra-dialog
header="Подтверждение заявки"
[size]="size"
[visible]="visible"
(visibleChange)="visible = $event"
(onShow)="log('onShow')"
Expand All @@ -34,6 +35,9 @@ const template = `
template
})
export class DialogBasicComponent {
/** Прокидывается из контрола size в панели Controls. */
@Input() size: ExtraDialogSize = 'default';

visible = false;
logEntries = signal<string[]>([]);

Expand All @@ -43,8 +47,12 @@ export class DialogBasicComponent {
}

export const Basic: StoryObj = {
render: () => ({
template: `<app-dialog-basic></app-dialog-basic>`
args: {
size: 'default'
},
render: (args) => ({
props: args,
template: `<app-dialog-basic [size]="size"></app-dialog-basic>`
}),
parameters: {
docs: {
Expand Down
63 changes: 37 additions & 26 deletions src/stories/components/dialog/examples/dialog-sizes.component.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { StoryObj } from '@storybook/angular';
import { ExtraButtonComponent } from '../../../../lib/components/button/button.component';
import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component';
import { ExtraDialogComponent, ExtraDialogSize } from '../../../../lib/components/dialog/dialog.component';

const template = `
<div class="bg-surface-ground p-4 flex gap-3 flex-wrap">
<extra-button label="SM" variant="secondary" (click)="size = 'sm'"></extra-button>
<extra-button label="Default" variant="secondary" (click)="size = 'default'"></extra-button>
<extra-button label="LG" variant="secondary" (click)="size = 'lg'"></extra-button>
<extra-button label="XLG" variant="secondary" (click)="size = 'xlg'"></extra-button>
<div class="bg-surface-ground p-4 flex flex-col gap-3">
<div class="flex gap-3 flex-wrap items-center">
@for (s of sizes; track s) {
<extra-button
[label]="s.toUpperCase()"
[variant]="s === size ? 'primary' : 'secondary'"
(click)="size = s"
></extra-button>
}
</div>

@for (s of sizes; track s) {
<extra-dialog
[header]="'Размер ' + s"
[size]="s"
[visible]="size === s"
(visibleChange)="size = $event ? s : null"
>
<p>Окно размера {{ s }}. Содержимое одинаковое — меняется ширина окна.</p>
</extra-dialog>
}
<div>
<extra-button [label]="'Открыть окно (' + size + ')'" (click)="visible = true"></extra-button>
</div>

<extra-dialog
[header]="'Размер ' + size"
[size]="size"
[visible]="visible"
(visibleChange)="visible = $event"
>
<p>Окно размера {{ size }}. Содержимое одинаковое — меняется ширина окна.</p>
</extra-dialog>
</div>
`;

Expand All @@ -31,8 +38,9 @@ const template = `
template
})
export class DialogSizesComponent {
sizes = ['sm', 'default', 'lg', 'xlg'];
size: string | null = null;
sizes: ExtraDialogSize[] = ['sm', 'default', 'lg', 'xlg'];
size: ExtraDialogSize = 'default';
visible = false;
}

export const Sizes: StoryObj = {
Expand All @@ -42,29 +50,32 @@ export const Sizes: StoryObj = {
parameters: {
docs: {
description: {
story: 'Размеры окна: sm, default, lg, xlg (ширина задаётся дизайн-токенами).'
story:
'Размеры окна: sm (280px), default (350px), lg (420px), xlg (630px) — ширина задаётся дизайн-токенами. Размер выбирается до открытия: отметьте нужный и нажмите «Открыть окно». Чтобы посмотреть другой размер, закройте окно и выберите заново.'
},
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { ExtraDialogComponent } from '@cdek-it/angular-ui-kit';
import { ExtraDialogComponent, ExtraDialogSize } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-dialog-sizes',
standalone: true,
imports: [ExtraDialogComponent],
template: \`
<extra-dialog header="Размер SM" size="sm" [visible]="visible" (visibleChange)="visible = $event">
<p>Маленькое окно</p>
</extra-dialog>

<extra-dialog header="Размер XLG" size="xlg" [visible]="visible" (visibleChange)="visible = $event">
<p>Очень широкое окно</p>
<extra-dialog
[header]="'Размер ' + size"
[size]="size"
[visible]="visible"
(visibleChange)="visible = $event"
>
<p>Окно размера {{ size }}</p>
</extra-dialog>
\`,
})
export class DialogSizesComponent {
size: ExtraDialogSize = 'default';
visible = false;
}
`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { ExtraSelectButtonComponent, ExtraSelectButtonOption } from '../../../../lib/components/select-button/select-button.component';

const template = `
<div class="bg-surface-ground p-4">
<div class="flex flex-col gap-4" style="max-width: 420px">
<extra-select-button [fluid]="true" [formControl]="control" [options]="options"></extra-select-button>
<extra-select-button [fluid]="true" [formControl]="wideControl" [options]="wideOptions"></extra-select-button>
<extra-select-button [formControl]="control" [options]="options"></extra-select-button>
</div>
</div>
`;
const styles = '';

@Component({
selector: 'app-select-button-fluid',
standalone: true,
imports: [ExtraSelectButtonComponent, ReactiveFormsModule],
template,
styles,
})
export class SelectButtonFluidComponent {
control = new FormControl('1');
wideControl = new FormControl('list');
options: ExtraSelectButtonOption[] = [
{ name: 'Option 1', code: '1' },
{ name: 'Option 2', code: '2' },
{ name: 'Option 3', code: '3' },
];
wideOptions: ExtraSelectButtonOption[] = [
{ name: 'Списком', code: 'list' },
{ name: 'Плиткой', code: 'grid' },
];
}

export const Fluid: StoryObj = {
name: 'Fluid',
render: () => ({
template: `<app-select-button-fluid></app-select-button-fluid>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: {
story:
'Растягивание на всю ширину контейнера (fluid). При `[fluid]="true"` группа занимает 100% ширины родителя, а сегменты делят её поровну — удобно для форм и мобильных раскладок. Третья группа — без `fluid`, для сравнения. Контейнер ограничен шириной 420px, чтобы эффект был нагляден.',
},
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { ExtraSelectButtonComponent, ExtraSelectButtonOption } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-select-button-fluid',
standalone: true,
imports: [ExtraSelectButtonComponent, ReactiveFormsModule],
template: \`
<div class="flex flex-col gap-4" style="max-width: 420px">
<extra-select-button [fluid]="true" [formControl]="control" [options]="options"></extra-select-button>
</div>
\`,
})
export class SelectButtonFluidComponent {
control = new FormControl('1');
options: ExtraSelectButtonOption[] = [
{ name: 'Option 1', code: '1' },
{ name: 'Option 2', code: '2' },
{ name: 'Option 3', code: '3' },
];
}
`,
},
},
},
};
17 changes: 17 additions & 0 deletions src/stories/components/select-button/select-button.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SelectButtonSelectedComponent, Selected as SelectedStory } from './exam
import { SelectButtonDisabledComponent, Disabled as DisabledStory } from './examples/select-button-disabled.component';
import { SelectButtonSemiDisabledComponent, SemiDisabled as SemiDisabledStory } from './examples/select-button-semi-disabled.component';
import { SelectButtonIconsComponent, WithIcons as WithIconsStory } from './examples/select-button-icons.component';
import { SelectButtonFluidComponent, Fluid as FluidStory } from './examples/select-button-fluid.component';

type SelectButtonArgs = ExtraSelectButtonComponent;

Expand All @@ -19,6 +20,7 @@ const meta: Meta<SelectButtonArgs> = {
SelectButtonDisabledComponent,
SelectButtonSemiDisabledComponent,
SelectButtonIconsComponent,
SelectButtonFluidComponent,
],
}),
],
Expand Down Expand Up @@ -71,6 +73,15 @@ import { ExtraSelectButtonComponent, ExtraSelectButtonOption } from '@cdek-it/an
type: { summary: 'boolean' },
},
},
fluid: {
control: 'boolean',
description: 'Растягивает группу на всю ширину контейнера',
table: {
category: 'Props',
defaultValue: { summary: 'false' },
type: { summary: 'boolean' },
},
},
options: {
control: 'object',
description: 'Массив опций',
Expand All @@ -97,6 +108,7 @@ export const Default: Story = {
if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`);
if (args.multiple) parts.push(`[multiple]="true"`);
if (!args.allowEmpty) parts.push(`[allowEmpty]="false"`);
if (args.fluid) parts.push(`[fluid]="true"`);

const template = `<extra-select-button\n ${parts.join('\n ')}\n></extra-select-button>`;

Expand All @@ -112,6 +124,7 @@ export const Default: Story = {
size: 'base',
multiple: false,
allowEmpty: true,
fluid: false,
},
parameters: {
docs: {
Expand All @@ -137,3 +150,7 @@ export const SemiDisabled: Story = SemiDisabledStory;
// ── With Icons ────────────────────────────────────────────────────────────────

export const WithIcons: Story = WithIconsStory;

// ── Fluid ─────────────────────────────────────────────────────────────────────

export const Fluid: Story = FluidStory;
Loading