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

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions src/lib/components/slider/slider.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { ChangeDetectionStrategy, Component, EventEmitter, forwardRef, Input, OnChanges, OnDestroy, Output, SimpleChanges } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { Slider } from 'primeng/slider';
import type { SliderSlideEndEvent } from 'primeng/slider';
import { Subscription } from 'rxjs';

export type SliderOrientation = 'horizontal' | 'vertical';

@Component({
selector: 'slider',
host: { style: 'display: block' },
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [Slider, ReactiveFormsModule],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SliderComponent),
multi: true,
},
],
template: `
<p-slider
[formControl]="control"
[min]="min"
[max]="max"
[step]="step"
[range]="range"
[orientation]="orientation"
(onSlideEnd)="onSlideEnd.emit($event)"
></p-slider>
`,
})
export class SliderComponent implements ControlValueAccessor, OnChanges, OnDestroy {
@Input() min = 0;
@Input() max = 100;
@Input() step: number | undefined = undefined;
@Input() range = false;
@Input() orientation: SliderOrientation = 'horizontal';
@Input() set disabled(value: boolean) {
value ? this.control.disable() : this.control.enable();
}
@Output() onSlideEnd = new EventEmitter<SliderSlideEndEvent>();

readonly control = new FormControl<number | number[]>(0, { nonNullable: true });

private _onChange: (value: number | number[]) => void = () => {};
private _onTouched: () => void = () => {};
private readonly _sub: Subscription;

constructor() {
this._sub = this.control.valueChanges.subscribe(v => this._onChange(v));
}

ngOnChanges(changes: SimpleChanges): void {
if (changes['range']) {
const current = this.control.value;
if (this.range && !Array.isArray(current)) {
this.control.setValue([this.min, this.max], { emitEvent: false });
} else if (!this.range && Array.isArray(current)) {
this.control.setValue(current[0], { emitEvent: false });
}
}
}

ngOnDestroy(): void {
this._sub.unsubscribe();
}

writeValue(value: number | number[]): void {
this.control.setValue(this.normalize(value ?? 0), { emitEvent: false });
}

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

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

setDisabledState(isDisabled: boolean): void {
isDisabled ? this.control.disable() : this.control.enable();
}

private normalize(value: number | number[]): number | number[] {
if (this.range && !Array.isArray(value)) return [this.min, this.max];
if (!this.range && Array.isArray(value)) return value[0];
return value;
}
}
12 changes: 12 additions & 0 deletions src/prime-preset/tokens/components/slider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Кастомная CSS-стилизация для компонента p-slider.
* Подключается в map-tokens.ts: `import { sliderCss } from './components/slider'`
*/
export const sliderCss = ({ dt }: { dt: (token: string) => string }): string => `
/* ─── Focus ring ползунка ─── */
.p-slider-handle:focus-visible {
outline: ${dt('slider.handle.focusRing.width')} ${dt('slider.handle.focusRing.style')} ${dt('focusRing.extend.success')};
outline-offset: ${dt('slider.handle.focusRing.offset')};
box-shadow: none;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Component } from '@angular/core';
import { StoryObj } from '@storybook/angular';
import { SliderComponent } from '../../../../lib/components/slider/slider.component';

const template = `
<div class="bg-surface-ground" style="width: 320px">
<slider [disabled]="true"></slider>
</div>
`;
const styles = '';

@Component({
selector: 'app-slider-disabled',
standalone: true,
imports: [SliderComponent],
template,
styles,
})
export class SliderDisabledComponent {}

export const Disabled: StoryObj = {
render: () => ({
template: `<app-slider-disabled></app-slider-disabled>`,
}),
parameters: {
docs: {
description: { story: 'Слайдер в отключённом состоянии.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { SliderComponent } from '@cdek-it/angular-ui-kit';
@Component({
selector: 'app-slider-disabled',
standalone: true,
imports: [SliderComponent],
template: \`
<slider [disabled]="true"></slider>
\`,
})
export class SliderDisabledComponent {}
`,
},
},
},
};
53 changes: 53 additions & 0 deletions src/stories/components/slider/examples/slider-range.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { SliderComponent } from '../../../../lib/components/slider/slider.component';

const template = `
<div class="bg-surface-ground">
<slider [min]="0" [max]="100" [range]="true" [(ngModel)]="value"></slider>
</div>
`;
const styles = '';

@Component({
selector: 'app-slider-range',
standalone: true,
imports: [SliderComponent, FormsModule],
template,
styles,
})
export class SliderRangeComponent {
value: number[] = [20, 80];
}

export const Range: StoryObj = {
render: () => ({
template: `<app-slider-range></app-slider-range>`,
}),
parameters: {
docs: {
description: { story: 'Выбор диапазона значений с двумя ползунками.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SliderComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-slider-range',
standalone: true,
imports: [SliderComponent, FormsModule],
template: \`
<slider [min]="0" [max]="100" [range]="true" [(ngModel)]="value"></slider>
\`,
})
export class SliderRangeComponent {
value: number[] = [20, 80];
}
`,
},
},
},
};
53 changes: 53 additions & 0 deletions src/stories/components/slider/examples/slider-step.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { SliderComponent } from '../../../../lib/components/slider/slider.component';

const template = `
<div class="bg-surface-ground">
<slider [min]="0" [max]="100" [step]="10" [(ngModel)]="value"></slider>
</div>
`;
const styles = '';

@Component({
selector: 'app-slider-step',
standalone: true,
imports: [SliderComponent, FormsModule],
template,
styles,
})
export class SliderStepComponent {
value = 50;
}

export const Step: StoryObj = {
render: () => ({
template: `<app-slider-step></app-slider-step>`,
}),
parameters: {
docs: {
description: { story: 'Слайдер с шагом изменения значения.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SliderComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-slider-step',
standalone: true,
imports: [SliderComponent, FormsModule],
template: \`
<slider [min]="0" [max]="100" [step]="10" [(ngModel)]="value"></slider>
\`,
})
export class SliderStepComponent {
value = 50;
}
`,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { SliderComponent } from '../../../../lib/components/slider/slider.component';

const template = `
<div class="bg-surface-ground" style="height: 220px">
<slider orientation="vertical" [(ngModel)]="value" style="height: 200px"></slider>
</div>
`;
const styles = '';

@Component({
selector: 'app-slider-vertical',
standalone: true,
imports: [SliderComponent, FormsModule],
template,
styles,
})
export class SliderVerticalComponent {
value = 50;
}

export const Vertical: StoryObj = {
render: () => ({
template: `<app-slider-vertical></app-slider-vertical>`,
}),
parameters: {
docs: {
description: { story: 'Слайдер с вертикальной ориентацией.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SliderComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-slider-vertical',
standalone: true,
imports: [SliderComponent, FormsModule],
template: \`
<div style="height: 220px">
<slider orientation="vertical" [(ngModel)]="value" style="height: 200px"></slider>
</div>
\`,
})
export class SliderVerticalComponent {
value = 50;
}
`,
},
},
},
};
Loading
Loading