Skip to content
Open
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
751 changes: 0 additions & 751 deletions .claude/CLAUDE-v1.6.md

This file was deleted.

82 changes: 82 additions & 0 deletions src/lib/components/listbox/listbox.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Component, EventEmitter, Input, Optional, Output, Self } from '@angular/core';
import { ControlValueAccessor, FormsModule, NgControl } from '@angular/forms';
import { Listbox, ListboxChangeEvent } from 'primeng/listbox';

@Component({
selector: 'listbox',
standalone: true,
imports: [Listbox, FormsModule],
template: `
<p-listbox
[options]="options"
[(ngModel)]="modelValue"
[optionLabel]="optionLabel"
[optionValue]="optionValue"
[multiple]="multiple"
[filter]="filter"
[filterPlaceHolder]="filterPlaceHolder"
[checkmark]="checkmark"
[group]="group"
[optionGroupLabel]="optionGroupLabel"
[optionGroupChildren]="optionGroupChildren"
[scrollHeight]="scrollHeight"
[emptyMessage]="emptyMessage"
[disabled]="isDisabled"
(onChange)="onChangeHandler($event)"
(onFocus)="onFocus.emit($event)"
(onBlur)="onBlur.emit($event)"
></p-listbox>
`,
})
export class ListboxComponent implements ControlValueAccessor {
@Input() options: any[] = [];
@Input() optionLabel = 'label';
@Input() optionValue: string | undefined = undefined;
@Input() multiple = false;
@Input() filter = false;
@Input() filterPlaceHolder: string | undefined = undefined;
@Input() checkmark = false;
@Input() group = false;
@Input() optionGroupLabel: string | undefined = undefined;
@Input() optionGroupChildren: string | undefined = undefined;
@Input() scrollHeight = '200px';
@Input() emptyMessage: string | undefined = undefined;

@Output() onFocus = new EventEmitter<FocusEvent>();
@Output() onBlur = new EventEmitter<FocusEvent>();

modelValue: any = null;

private _disabled = false;
private _onChange: (value: any) => void = () => {};
private _onTouched: () => void = () => {};

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

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

onChangeHandler(event: ListboxChangeEvent): void {
this._onChange(event.value);
this._onTouched();
}

writeValue(value: any): void {
this.modelValue = value;
}

registerOnChange(fn: (value: any) => void): void {
this._onChange = fn;
}

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

setDisabledState(isDisabled: boolean): void {
this._disabled = isDisabled;
}
}
10 changes: 10 additions & 0 deletions src/prime-preset/map-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import type { AuraBaseDesignTokens } from '@primeuix/themes/aura/base';

import tokens from './tokens/tokens.json';
import { avatarCss } from './tokens/components/avatar';
import { breadcrumbCss } from './tokens/components/breadcrumb';
import { buttonCss } from './tokens/components/button';
import { listboxCss } from './tokens/components/listbox';
import { tooltipCss } from './tokens/components/tooltip';

const presetTokens: Preset<AuraBaseDesignTokens> = {
Expand All @@ -16,10 +18,18 @@ const presetTokens: Preset<AuraBaseDesignTokens> = {
...(tokens.components.avatar as unknown as ComponentsDesignTokens['avatar']),
css: avatarCss,
},
breadcrumb: {
...(tokens.components.breadcrumb as unknown as ComponentsDesignTokens['breadcrumb']),
css: breadcrumbCss,
},
button: {
...(tokens.components.button as unknown as ComponentsDesignTokens['button']),
css: buttonCss,
},
listbox: {
...(tokens.components.listbox as unknown as ComponentsDesignTokens['listbox']),
css: listboxCss,
},
tooltip: {
...(tokens.components.tooltip as unknown as ComponentsDesignTokens['tooltip']),
css: tooltipCss,
Expand Down
49 changes: 49 additions & 0 deletions src/prime-preset/tokens/components/listbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const listboxCss = ({ dt }: { dt: (token: string) => string }): string => `
/* ─── Listbox extend: публикуем кастомные переменные в :root ─── */
:root {
--p-listbox-extend-ext-option-gap: ${dt('listbox.extend.extOption.gap')};
--p-listbox-extend-ext-option-label-gap: ${dt('listbox.extend.extOption.label.gap')};
--p-listbox-extend-ext-option-caption-color: ${dt('listbox.extend.extOption.caption.color')};
--p-listbox-extend-ext-option-caption-striped-color: ${dt('listbox.extend.extOption.caption.stripedColor')};
}

/* ─── Расположение элемента списка ─── */
.p-listbox-option {
display: flex;
align-items: center;
gap: var(--p-listbox-extend-ext-option-gap);
}

/* Многострочный контент (иконка + label-group): выравнивание по верху */
.p-listbox-option:has(.p-listbox-option-label-group) {
align-items: flex-start;
}

.p-listbox-option:has(.p-listbox-option-check-icon) {
gap: unset;
}

/* ─── Группа: текст + подпись ─── */
.p-listbox-option-label-group {
display: flex;
flex-direction: column;
gap: var(--p-listbox-extend-ext-option-label-gap);
}

/* ─── Подпись элемента списка ─── */
.p-listbox-option-caption {
color: var(--p-listbox-extend-ext-option-caption-color);
font-size: ${dt('fonts.fontSize.200')};
font-family: ${dt('fonts.fontFamily.heading')};
}

/* ─── Галочка выбора ─── */
.p-listbox-check-icon {
margin-inline-start: ${dt('listbox.checkmark.gutterStart')};
margin-inline-end: ${dt('listbox.checkmark.gutterEnd')};
}

.p-listbox .p-listbox-list .p-listbox-option.p-listbox-option-selected .p-listbox-option-check-icon {
color: ${dt('listbox.option.selectedColor')};
}
`;
2 changes: 1 addition & 1 deletion src/prime-preset/tokens/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -3215,7 +3215,7 @@
"optionGroup": {
"background": "{list.optionGroup.background}",
"color": "{list.optionGroup.color}",
"fontWeight": "{fonts.fontWeight.demibold}",
"fontWeight": "{fonts.fontWeight.regular}",
"padding": "{list.option.padding}"
},
"checkmark": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { ListboxComponent } from '../../../../lib/components/listbox/listbox.component';

const options = [
{ label: 'New York', value: 'NY' },
{ label: 'Rome', value: 'RM' },
{ label: 'London', value: 'LDN' },
{ label: 'Istanbul', value: 'IST' },
{ label: 'Paris', value: 'PRS' },
];

const template = `
<listbox [formControl]="ctrl" [options]="options" [checkmark]="true"></listbox>
`;
const styles = '';

@Component({
selector: 'app-listbox-checkmark',
standalone: true,
imports: [ListboxComponent, ReactiveFormsModule],
template,
styles,
})
export class ListboxCheckmarkComponent {
ctrl = new FormControl(null);
options = options;
}

export const Checkmark: StoryObj = {
render: () => ({
template: `<app-listbox-checkmark></app-listbox-checkmark>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Галочка рядом с выбранным элементом.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ListboxComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-listbox-checkmark',
standalone: true,
imports: [ListboxComponent, ReactiveFormsModule],
template: \`
<listbox [formControl]="ctrl" [options]="options" [checkmark]="true"></listbox>
\`,
})
export class ListboxCheckmarkComponent {
ctrl = new FormControl(null);
options = [
{ label: 'New York', value: 'NY' },
{ label: 'Rome', value: 'RM' },
{ label: 'London', value: 'LDN' },
];
}
`,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { Listbox } from 'primeng/listbox';
import { SharedModule } from 'primeng/api';

const options = [
{ name: 'Profile', description: 'Manage your account', icon: 'ti ti-user' },
{ name: 'Settings', description: 'App preferences', icon: 'ti ti-settings' },
{ name: 'Messages', description: 'Your inbox', icon: 'ti ti-message' },
];

const template = `
<p-listbox [formControl]="ctrl" [options]="options" optionLabel="name">
<ng-template pTemplate="item" let-item>
<i [class]="item.icon"></i>
<div class="p-listbox-option-label-group">
<span>{{ item.name }}</span>
<small class="p-listbox-option-caption">{{ item.description }}</small>
</div>
</ng-template>
</p-listbox>
`;
const styles = '';

@Component({
selector: 'app-listbox-custom',
standalone: true,
imports: [Listbox, SharedModule, ReactiveFormsModule],
template,
styles,
})
export class ListboxCustomComponent {
ctrl = new FormControl(null);
options = options;
}

export const Custom: StoryObj = {
render: () => ({
template: `<app-listbox-custom></app-listbox-custom>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Кастомный шаблон элемента с иконкой и подписью.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Listbox } from 'primeng/listbox';
import { SharedModule } from 'primeng/api';

@Component({
selector: 'app-listbox-custom',
standalone: true,
imports: [Listbox, SharedModule, ReactiveFormsModule],
template: \`
<p-listbox [formControl]="ctrl" [options]="options" optionLabel="name">
<ng-template pTemplate="item" let-item>
<i [class]="item.icon"></i>
<div class="p-listbox-option-label-group">
<span>{{ item.name }}</span>
<small class="p-listbox-option-caption">{{ item.description }}</small>
</div>
</ng-template>
</p-listbox>
\`,
})
export class ListboxCustomComponent {
ctrl = new FormControl(null);
options = [
{ name: 'Profile', description: 'Manage your account', icon: 'ti ti-user' },
{ name: 'Settings', description: 'App preferences', icon: 'ti ti-settings' },
{ name: 'Messages', description: 'Your inbox', icon: 'ti ti-message' },
];
}
`,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { ListboxComponent } from '../../../../lib/components/listbox/listbox.component';

const options = [
{ label: 'New York', value: 'NY' },
{ label: 'Rome', value: 'RM' },
{ label: 'London', value: 'LDN' },
{ label: 'Istanbul', value: 'IST' },
{ label: 'Paris', value: 'PRS' },
];

const template = `
<listbox [formControl]="ctrl" [options]="options"></listbox>
`;
const styles = '';

@Component({
selector: 'app-listbox-disabled',
standalone: true,
imports: [ListboxComponent, ReactiveFormsModule],
template,
styles,
})
export class ListboxDisabledComponent {
ctrl = new FormControl({ value: null, disabled: true });
options = options;
}

export const Disabled: StoryObj = {
render: () => ({
template: `<app-listbox-disabled></app-listbox-disabled>`,
}),
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Список в отключённом состоянии — взаимодействие заблокировано.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ListboxComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-listbox-disabled',
standalone: true,
imports: [ListboxComponent, ReactiveFormsModule],
template: \`
<listbox [formControl]="ctrl" [options]="options"></listbox>
\`,
})
export class ListboxDisabledComponent {
ctrl = new FormControl({ value: null, disabled: true });
options = [
{ label: 'New York', value: 'NY' },
{ label: 'Rome', value: 'RM' },
{ label: 'London', value: 'LDN' },
];
}
`,
},
},
},
};
Loading
Loading