Skip to content
91 changes: 91 additions & 0 deletions src/lib/components/select-button/select-button.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Component, EventEmitter, Input, Optional, Output, Self } from '@angular/core';
import { NgClass } from '@angular/common';
import { ControlValueAccessor, FormsModule, NgControl } from '@angular/forms';
import { SelectButton } from 'primeng/selectbutton';
import { SharedModule } from 'primeng/api';

export interface SelectButtonItem {
label: string;
value: string;
icon?: string;
disabled?: boolean;
}

@Component({
selector: 'select-button',
standalone: true,
imports: [SelectButton, SharedModule, FormsModule, NgClass],
template: `
<p-selectbutton
[options]="options"
[ngModel]="value"
(ngModelChange)="onValueChange($event)"
[optionLabel]="optionLabel"
[optionValue]="optionValue"
[optionDisabled]="optionDisabled"
[multiple]="multiple"
[allowEmpty]="allowEmpty"
[disabled]="isDisabled"
[ngClass]="sizeClass"
>
<ng-template pTemplate="item" let-item>
@if (item['icon']) {
<i [class]="item['icon']"></i>
}
<span>{{ item[optionLabel] }}</span>
</ng-template>
</p-selectbutton>
`,
})
export class SelectButtonComponent implements ControlValueAccessor {
@Input() options: any[] = [];
@Input() optionLabel = 'label';
@Input() optionValue = 'value';
@Input() optionDisabled = 'disabled';
@Input() size: 'default' | 'sm' | 'lg' | 'xlg' = 'default';
@Input() multiple = false;
@Input() allowEmpty = true;

@Output() valueChange = new EventEmitter<string | string[]>();

value: string | string[] = '';

private _disabled = false;
private onChange = (_: string | string[]) => {};
private onTouched = () => {};

constructor(@Optional() @Self() private ngControl: NgControl) {
if (ngControl) ngControl.valueAccessor = this;
}

get isDisabled(): boolean {
return this._disabled;
}

get sizeClass(): string {
return this.size === 'default' ? '' : `p-selectbutton-${this.size}`;
}

writeValue(value: string | string[]): void {
this.value = value ?? '';
}

registerOnChange(fn: (value: string | string[]) => void): void {
this.onChange = fn;
}

registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}

setDisabledState(isDisabled: boolean): void {
this._disabled = isDisabled;
}

onValueChange(newValue: string | string[]): void {
this.value = newValue;
this.onChange(newValue);
this.onTouched();
this.valueChange.emit(newValue);
}
}
5 changes: 5 additions & 0 deletions src/prime-preset/map-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { tagCss } from './tokens/components/tag';
import { timelineCss } from './tokens/components/timeline';
import { tooltipCss } from './tokens/components/tooltip';
import { megamenuCss } from './tokens/components/megamenu';
import { selectbuttonCss } from './tokens/components/selectbutton';

const presetTokens: Preset<AuraBaseDesignTokens> = {
primitive: tokens.primitive as unknown as AuraBaseDesignTokens['primitive'],
Expand Down Expand Up @@ -59,6 +60,10 @@ const presetTokens: Preset<AuraBaseDesignTokens> = {
...(tokens.components.megamenu as unknown as ComponentsDesignTokens['megamenu']),
css: megamenuCss,
},
selectbutton: {
...(tokens.components.selectbutton as unknown as ComponentsDesignTokens['selectbutton']),
css: selectbuttonCss,
},
} as ComponentsDesignTokens,
};

Expand Down
90 changes: 90 additions & 0 deletions src/prime-preset/tokens/components/selectbutton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
export const selectbuttonCss = ({ dt }: { dt: (token: string) => string }): string => `
.p-selectbutton.p-component {
background: ${dt('selectbutton.colorScheme.light.extend.background')};
padding: ${dt('selectbutton.extend.paddingY')} ${dt('selectbutton.extend.paddingX')};
gap: ${dt('selectbutton.extend.gap')};
font-family: ${dt('fonts.fontFamily.heading')};
font-weight: ${dt('fonts.fontWeight.demibold')};
}

.p-selectbutton.p-component .p-togglebutton.p-component {
font-family: ${dt('fonts.fontFamily.heading')};
font-weight: ${dt('fonts.fontWeight.demibold')};
line-height: ${dt('fonts.lineHeight.500')};
height: ${dt('controls.iconOnly.700')};
border-radius: ${dt('selectbutton.extend.ext.borderRadius')};
}

.p-selectbutton.p-component .p-togglebutton .p-togglebutton-label,
.p-selectbutton.p-component .p-togglebutton .p-togglebutton-content > span {
line-height: ${dt('fonts.lineHeight.400')};
}

.p-selectbutton.p-component .p-togglebutton.p-togglebutton-checked.p-component,
.p-selectbutton.p-component .p-togglebutton.p-togglebutton-checked.p-component:hover {
background: ${dt('selectbutton.extend.checkedBackground')};
border-radius: ${dt('selectbutton.extend.ext.borderRadius')};
border-color: ${dt('selectbutton.extend.checkedBorderColor')};
color: ${dt('selectbutton.extend.checkedColor')};
}

.p-selectbutton.p-component .p-togglebutton.p-component:not(:disabled):not(.p-togglebutton-checked):hover {
border-radius: ${dt('selectbutton.extend.ext.borderRadius')};
border-color: ${dt('togglebutton.extend.hoverBorderColor')};
}

/* Size: sm */
.p-selectbutton.p-selectbutton-sm.p-component .p-togglebutton.p-component {
line-height: ${dt('fonts.lineHeight.300')};
height: ${dt('controls.iconOnly.600')};
}

.p-selectbutton.p-selectbutton-sm.p-component .p-togglebutton .p-togglebutton-label,
.p-selectbutton.p-selectbutton-sm.p-component .p-togglebutton .p-togglebutton-content > span {
line-height: ${dt('fonts.lineHeight.250')};
}

.p-selectbutton.p-selectbutton-sm.p-component .p-togglebutton .p-togglebutton-icon,
.p-selectbutton.p-selectbutton-sm.p-component .p-togglebutton i {
font-size: ${dt('selectbutton.extend.iconSize.sm')};
}

/* Size: md (base) */
.p-selectbutton.p-component:not(.p-selectbutton-sm):not(.p-selectbutton-lg):not(.p-selectbutton-xlg) .p-togglebutton .p-togglebutton-icon,
.p-selectbutton.p-component:not(.p-selectbutton-sm):not(.p-selectbutton-lg):not(.p-selectbutton-xlg) .p-togglebutton i {
font-size: ${dt('selectbutton.extend.iconSize.md')};
}

/* Size: lg */
.p-selectbutton.p-selectbutton-lg.p-component .p-togglebutton.p-component {
line-height: ${dt('fonts.lineHeight.550')};
height: ${dt('controls.iconOnly.850')};
}

.p-selectbutton.p-selectbutton-lg.p-component .p-togglebutton .p-togglebutton-label,
.p-selectbutton.p-selectbutton-lg.p-component .p-togglebutton .p-togglebutton-content > span {
line-height: ${dt('fonts.lineHeight.550')};
}

.p-selectbutton.p-selectbutton-lg.p-component .p-togglebutton .p-togglebutton-icon,
.p-selectbutton.p-selectbutton-lg.p-component .p-togglebutton i {
font-size: ${dt('selectbutton.extend.iconSize.lg')};
}

/* Size: xlg */
.p-selectbutton.p-selectbutton-xlg.p-component .p-togglebutton.p-component {
font-size: ${dt('fonts.fontSize.600')};
line-height: ${dt('fonts.lineHeight.550')};
height: ${dt('controls.iconOnly.900')};
}

.p-selectbutton.p-selectbutton-xlg.p-component .p-togglebutton .p-togglebutton-label,
.p-selectbutton.p-selectbutton-xlg.p-component .p-togglebutton .p-togglebutton-content > span {
line-height: ${dt('fonts.lineHeight.700')};
}

.p-selectbutton.p-selectbutton-xlg.p-component .p-togglebutton .p-togglebutton-icon,
.p-selectbutton.p-selectbutton-xlg.p-component .p-togglebutton i {
font-size: ${dt('selectbutton.extend.iconSize.xlg')};
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { SelectButtonComponent, SelectButtonItem } from '../../../../lib/components/select-button/select-button.component';

const template = `
<div class="bg-surface-ground p-4">
<select-button [formControl]="control" [options]="options"></select-button>
</div>
`;
const styles = '';

@Component({
selector: 'app-select-button-disabled',
standalone: true,
imports: [SelectButtonComponent, ReactiveFormsModule],
template,
styles,
})
export class SelectButtonDisabledComponent {
control = new FormControl({ value: '1', disabled: true });
options: SelectButtonItem[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
];
}

export const Disabled: StoryObj = {
name: 'Disabled',
render: () => ({
template: `<app-select-button-disabled></app-select-button-disabled>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Весь компонент отключён через `FormControl`.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { SelectButtonComponent, SelectButtonItem } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-select-button-disabled',
standalone: true,
imports: [SelectButtonComponent, ReactiveFormsModule],
template: \`
<select-button [formControl]="control" [options]="options"></select-button>
\`,
})
export class SelectButtonDisabledComponent {
control = new FormControl({ value: '1', disabled: true });
options: SelectButtonItem[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
];
}
`,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { SelectButtonComponent, SelectButtonItem } from '../../../../lib/components/select-button/select-button.component';

const template = `
<div class="bg-surface-ground p-4">
<select-button [(ngModel)]="value" [options]="options"></select-button>
</div>
`;
const styles = '';

@Component({
selector: 'app-select-button-icons',
standalone: true,
imports: [SelectButtonComponent, FormsModule],
template,
styles,
})
export class SelectButtonIconsComponent {
value = 'left';
options: SelectButtonItem[] = [
{ label: 'Left', value: 'left', icon: 'ti ti-align-left' },
{ label: 'Center', value: 'center', icon: 'ti ti-align-center' },
{ label: 'Right', value: 'right', icon: 'ti ti-align-right' },
];
}

export const WithIcons: StoryObj = {
name: 'With Icons',
render: () => ({
template: `<app-select-button-icons></app-select-button-icons>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Опции с иконками — иконка отображается автоматически если задано поле `icon`.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SelectButtonComponent, SelectButtonItem } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-select-button-icons',
standalone: true,
imports: [SelectButtonComponent, FormsModule],
template: \`
<select-button [(ngModel)]="value" [options]="options"></select-button>
\`,
})
export class SelectButtonIconsComponent {
value = 'left';
options: SelectButtonItem[] = [
{ label: 'Left', value: 'left', icon: 'ti ti-align-left' },
{ label: 'Center', value: 'center', icon: 'ti ti-align-center' },
{ label: 'Right', value: 'right', icon: 'ti ti-align-right' },
];
}
`,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { SelectButtonComponent, SelectButtonItem } from '../../../../lib/components/select-button/select-button.component';

const template = `
<div class="bg-surface-ground p-4">
<select-button [(ngModel)]="value" [options]="options"></select-button>
</div>
`;
const styles = '';

@Component({
selector: 'app-select-button-selected',
standalone: true,
imports: [SelectButtonComponent, FormsModule],
template,
styles,
})
export class SelectButtonSelectedComponent {
value = '2';
options: SelectButtonItem[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
];
}

export const Selected: StoryObj = {
name: 'Selected',
render: () => ({
template: `<app-select-button-selected></app-select-button-selected>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Второй вариант выбран по умолчанию.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SelectButtonComponent, SelectButtonItem } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-select-button-selected',
standalone: true,
imports: [SelectButtonComponent, FormsModule],
template: \`
<select-button [(ngModel)]="value" [options]="options"></select-button>
\`,
})
export class SelectButtonSelectedComponent {
value = '2';
options: SelectButtonItem[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
];
}
`,
},
},
},
};
Loading