diff --git a/src/lib/components/date-picker/date-picker.component.ts b/src/lib/components/date-picker/date-picker.component.ts
new file mode 100644
index 00000000..d1b87466
--- /dev/null
+++ b/src/lib/components/date-picker/date-picker.component.ts
@@ -0,0 +1,395 @@
+import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { DatePicker } from 'primeng/datepicker';
+import { InputNumber } from 'primeng/inputnumber';
+import { PrimeTemplate } from 'primeng/api';
+import { Select } from 'primeng/select';
+import { Button } from 'primeng/button';
+
+const MONTHS = [
+ { name: 'Январь', value: 0 },
+ { name: 'Февраль', value: 1 },
+ { name: 'Март', value: 2 },
+ { name: 'Апрель', value: 3 },
+ { name: 'Май', value: 4 },
+ { name: 'Июнь', value: 5 },
+ { name: 'Июль', value: 6 },
+ { name: 'Август', value: 7 },
+ { name: 'Сентябрь', value: 8 },
+ { name: 'Октябрь', value: 9 },
+ { name: 'Ноябрь', value: 10 },
+ { name: 'Декабрь', value: 11 },
+];
+
+const YEARS = (() => {
+ const result = [];
+ for (let y = 1990; y <= 2035; y++) {
+ result.push({ name: String(y), value: y });
+ }
+ return result;
+})();
+
+export type DatePickerSize = 'small' | 'medium' | 'large' | 'xlarge';
+export type DatePickerSelectionMode = 'single' | 'multiple' | 'range';
+export type DatePickerIconDisplay = 'input' | 'button';
+
+@Component({
+ selector: 'date-picker',
+ host: { style: 'display: inline-flex' },
+ standalone: true,
+ imports: [DatePicker, InputNumber, PrimeTemplate, FormsModule, Select, Button],
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ @if (showClear) {
+
+
+
+ }
+ @if (showTime) {
+
+
+
+ }
+
+ `,
+})
+export class DatePickerComponent implements AfterViewInit, OnDestroy {
+ @ViewChild('dpRef') dpRef!: DatePicker;
+
+ readonly months = MONTHS;
+ readonly years = YEARS;
+
+ dpCurrentMonth = new Date().getMonth();
+ dpCurrentYear = new Date().getFullYear();
+
+ @Input() value: Date | Date[] | null = null;
+ @Output() valueChange = new EventEmitter();
+
+ @Input() dateFormat = 'dd.mm.yy';
+ @Input() selectionMode: DatePickerSelectionMode = 'single';
+ @Input() size: DatePickerSize = 'medium';
+ @Input() showIcon = true;
+ @Input() iconDisplay: DatePickerIconDisplay = 'input';
+ @Input() inline = false;
+ @Input() showButtonBar = false;
+ @Input() showTime = false;
+ @Input() hourFormat = '24';
+ @Input() showClear = false;
+ @Input() placeholder: string | undefined = undefined;
+ @Input() disabled = false;
+ @Input() readonly = false;
+ @Input() invalid = false;
+ @Input() minDate: Date | undefined = undefined;
+ @Input() maxDate: Date | undefined = undefined;
+ @Input() view: 'date' | 'month' | 'year' = 'date';
+ @Input() showOtherMonths = true;
+ @Input() selectOtherMonths = false;
+
+ @Output() onSelect = new EventEmitter();
+ @Output() onMonthChange = new EventEmitter();
+ @Output() onYearChange = new EventEmitter();
+ @Output() onClear = new EventEmitter();
+
+ get primeSize(): 'small' | 'large' | undefined {
+ if (this.size === 'small') return 'small';
+ if (this.size === 'large' || this.size === 'xlarge') return 'large';
+ return undefined;
+ }
+
+ ngAfterViewInit(): void {
+ // Sync initial month/year for inline mode (panel renders immediately)
+ if (this.inline) {
+ this.syncCurrentDate();
+ }
+ }
+
+ syncCurrentDate(): void {
+ if (this.dpRef) {
+ this.dpCurrentMonth = this.dpRef.currentMonth;
+ this.dpCurrentYear = this.dpRef.currentYear;
+ }
+ }
+
+ onMonthChangeHandler(event: { month: number; year: number }): void {
+ this.dpCurrentMonth = event.month - 1;
+ this.dpCurrentYear = event.year;
+ this.onMonthChange.emit(event);
+ }
+
+ onYearChangeHandler(event: { month: number; year: number }): void {
+ this.dpCurrentMonth = event.month - 1;
+ this.dpCurrentYear = event.year;
+ this.onYearChange.emit(event);
+ }
+
+ onMonthSelect(month: number): void {
+ this.dpCurrentMonth = month;
+ this.dpRef?.createMonths(month, this.dpCurrentYear);
+ }
+
+ onYearSelect(year: number): void {
+ this.dpCurrentYear = year;
+ this.dpRef?.createMonths(this.dpCurrentMonth, year);
+ }
+
+ navPrev(event: MouseEvent): void {
+ this.dpRef?.navBackward(event);
+ }
+
+ navNext(event: MouseEvent): void {
+ this.dpRef?.navForward(event);
+ }
+
+ onValueChange(val: Date | Date[] | null): void {
+ this.value = val;
+ this.valueChange.emit(val);
+ if (this.selectionMode === 'range' && Array.isArray(val)) {
+ if (val[1]) {
+ this.rangeStart = null;
+ if (!this.inline) {
+ (this.dpRef as any).hideOverlay();
+ }
+ } else {
+ this.rangeStart = val[0] ?? null;
+ }
+ }
+ }
+
+ onHourInput(event: { value: number | null }): void {
+ const clamped = Math.min(23, Math.max(0, event.value ?? 0));
+ this.dpRef.currentHour = clamped;
+ }
+
+ onMinuteInput(event: { value: number | null }): void {
+ const clamped = Math.min(59, Math.max(0, event.value ?? 0));
+ this.dpRef.currentMinute = clamped;
+ }
+
+ onTimeBlur(): void {
+ (this.dpRef as any).updateTime();
+ }
+
+ // ── Range hover preview ──────────────────────────────────────────────────
+
+ private panelMouseoverHandler: ((e: Event) => void) | null = null;
+ private panelMouseleaveHandler: (() => void) | null = null;
+ private rangeStart: Date | null = null;
+
+ onPanelShow(): void {
+ this.syncCurrentDate();
+ if (this.selectionMode === 'range') {
+ this.attachRangePreview();
+ }
+ }
+
+ onPanelClose(): void {
+ this.detachRangePreview();
+ }
+
+ onDateSelected(event: any): void {
+ this.onSelect.emit(event);
+ }
+
+ ngOnDestroy(): void {
+ this.detachRangePreview();
+ }
+
+ private attachRangePreview(): void {
+ this.removeRangeListeners();
+
+ setTimeout(() => {
+ const panel = this.getPanelElement();
+ if (!panel) return;
+
+ this.panelMouseoverHandler = (e: Event) => {
+ if (!this.rangeStart) return;
+ const dayEl = (e.target as HTMLElement).closest('.p-datepicker-day') as HTMLElement;
+ if (!dayEl) return;
+ const dateKey = dayEl.getAttribute('data-date');
+ if (!dateKey) return;
+ const hovered = this.parseDateKey(dateKey);
+ if (!hovered) return;
+ this.clearHoverPreview(panel);
+ this.applyRangePreview(panel, this.rangeStart, hovered);
+ };
+
+ this.panelMouseleaveHandler = () => {
+ this.clearHoverPreview(panel);
+ };
+
+ panel.addEventListener('mouseover', this.panelMouseoverHandler);
+ panel.addEventListener('mouseleave', this.panelMouseleaveHandler);
+ });
+ }
+
+ private removeRangeListeners(): void {
+ const panel = this.getPanelElement();
+ if (panel) {
+ if (this.panelMouseoverHandler) {
+ panel.removeEventListener('mouseover', this.panelMouseoverHandler);
+ }
+ if (this.panelMouseleaveHandler) {
+ panel.removeEventListener('mouseleave', this.panelMouseleaveHandler);
+ }
+ }
+ this.panelMouseoverHandler = null;
+ this.panelMouseleaveHandler = null;
+ }
+
+ private detachRangePreview(): void {
+ const panel = this.getPanelElement();
+ if (panel) this.clearHoverPreview(panel);
+ this.removeRangeListeners();
+ }
+
+ private getPanelElement(): HTMLElement | null {
+ return (this.dpRef as any)?.content?.nativeElement
+ ?? document.querySelector('.p-datepicker-panel');
+ }
+
+ private parseDateKey(key: string): Date | null {
+ const parts = key.split('-').map(Number);
+ if (parts.length !== 3 || parts.some(isNaN)) return null;
+ return new Date(parts[0], parts[1], parts[2]);
+ }
+
+ private applyRangePreview(panel: HTMLElement, start: Date, end: Date): void {
+ const [from, to] = start <= end ? [start, end] : [end, start];
+ const days = panel.querySelectorAll('.p-datepicker-day[data-date]');
+ days.forEach((el) => {
+ const key = el.getAttribute('data-date');
+ if (!key) return;
+ const d = this.parseDateKey(key);
+ if (!d) return;
+ const inRange = d > from && d < to;
+ const isEdge = d.getTime() === from.getTime() || d.getTime() === to.getTime();
+ if (inRange) {
+ el.classList.add('p-datepicker-day-selected-range');
+ el.setAttribute('data-hover-preview', '');
+ }
+ if (isEdge && !el.classList.contains('p-datepicker-day-selected')) {
+ el.classList.add('p-datepicker-day-selected');
+ el.setAttribute('data-hover-preview', '');
+ }
+ });
+ }
+
+ private clearHoverPreview(panel: HTMLElement): void {
+ panel.querySelectorAll('[data-hover-preview]').forEach((el) => {
+ el.classList.remove('p-datepicker-day-selected-range', 'p-datepicker-day-selected');
+ el.removeAttribute('data-hover-preview');
+ });
+ }
+}
diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts
index 185240a8..7bcd16db 100644
--- a/src/prime-preset/map-tokens.ts
+++ b/src/prime-preset/map-tokens.ts
@@ -7,11 +7,12 @@ import { avatarCss } from './tokens/components/avatar';
import { buttonCss } from './tokens/components/button';
import { cardCss } from './tokens/components/card';
import { checkboxCss } from './tokens/components/checkbox';
+import { datePickerCss } from './tokens/components/date-picker';
import { inputtextCss } from './tokens/components/inputtext';
+import { megamenuCss } from './tokens/components/megamenu';
import { progressspinnerCss } from './tokens/components/progressspinner';
import { tagCss } from './tokens/components/tag';
import { tooltipCss } from './tokens/components/tooltip';
-import { megamenuCss } from './tokens/components/megamenu';
const presetTokens: Preset = {
primitive: tokens.primitive as unknown as AuraBaseDesignTokens['primitive'],
@@ -34,14 +35,22 @@ const presetTokens: Preset = {
...(tokens.components.button as unknown as ComponentsDesignTokens['button']),
css: buttonCss,
},
- progressspinner: {
- ...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']),
- css: progressspinnerCss,
+ datepicker: {
+ ...(tokens.components.datepicker as unknown as ComponentsDesignTokens['datepicker']),
+ css: datePickerCss,
},
inputtext: {
...(tokens.components.inputtext as unknown as ComponentsDesignTokens['inputtext']),
css: inputtextCss,
},
+ megamenu: {
+ ...(tokens.components.megamenu as unknown as ComponentsDesignTokens['megamenu']),
+ css: megamenuCss,
+ },
+ progressspinner: {
+ ...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']),
+ css: progressspinnerCss,
+ },
tag: {
...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']),
css: tagCss,
@@ -50,10 +59,6 @@ const presetTokens: Preset = {
...(tokens.components.tooltip as unknown as ComponentsDesignTokens['tooltip']),
css: tooltipCss,
},
- megamenu: {
- ...(tokens.components.megamenu as unknown as ComponentsDesignTokens['megamenu']),
- css: megamenuCss,
- },
} as ComponentsDesignTokens,
};
diff --git a/src/prime-preset/tokens/components/date-picker.ts b/src/prime-preset/tokens/components/date-picker.ts
new file mode 100644
index 00000000..27095a74
--- /dev/null
+++ b/src/prime-preset/tokens/components/date-picker.ts
@@ -0,0 +1,167 @@
+/**
+ * Кастомная CSS-стилизация для компонента p-datepicker.
+ * Подключается в map-tokens.ts: `import { datePickerCss } from './components/date-picker'`
+ */
+export const datePickerCss = ({ dt }: { dt: (token: string) => string }): string => `
+
+.p-datepicker.p-datepicker {
+ display: inline-flex;
+ position: relative;
+ max-width: fit-content;
+}
+
+.p-datepicker-panel.p-datepicker-panel {
+ box-shadow: ${dt('datepicker.panel.shadow')};
+ overflow: hidden;
+}
+
+/* Скрываем нативный заголовок с кнопками выбора месяца/года;
+ кастомный заголовок (pTemplate="header") рендерится вне .p-datepicker-calendar */
+.p-datepicker-calendar .p-datepicker-header {
+ display: none;
+}
+
+.p-datepicker-custom-header.p-datepicker-header {
+ gap: ${dt('datepicker.title.gap')};
+ padding: ${dt('datepicker.header.padding')};
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.p-datepicker-title {
+ display: flex;
+ align-items: center;
+ gap: ${dt('datepicker.title.gap')};
+ margin: 0 auto;
+}
+
+.p-datepicker-year-select {
+ min-width: ${dt('datepicker.extend.extSelectYear.minWidth')};
+}
+
+.p-datepicker-custom-header .p-button.p-button {
+ width: ${dt('datepicker.dropdown.width')};
+ height: ${dt('datepicker.dropdown.width')};
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.p-datepicker-today.p-datepicker-today > .p-datepicker-day:not(.p-datepicker-day-selected) {
+ border: ${dt('form.borderWidth')} solid ${dt(
+ 'datepicker.extend.extToday.borderColor'
+)};
+}
+
+.p-datepicker-today.p-datepicker-today:hover > .p-datepicker-day:not(.p-datepicker-day-selected),
+.p-datepicker-day-preview.p-datepicker-day-preview:not(.p-datepicker-day-selected) {
+ background: ${dt('datepicker.extend.extToday.hoverBackground')};
+}
+
+.p-datepicker-day-preview.p-datepicker-day-preview:not(.p-datepicker-day-selected) {
+ color: ${dt('datepicker.date.color')};
+}
+
+.p-datepicker-day-view.p-datepicker-day-view {
+ width: stretch;
+ border-collapse: separate;
+ table-layout: auto;
+ margin: ${dt('datepicker.dayView.margin')};
+}
+
+/* ─── Weekday header text ─── */
+.p-datepicker-weekday {
+ font-family: ${dt('fonts.fontFamily.base')};
+ font-size: ${dt('fonts.fontSize.300')};
+ font-weight: ${dt('fonts.fontWeight.demibold')};
+}
+
+/* ─── Day cell text ─── */
+.p-datepicker-day {
+ font-family: ${dt('fonts.fontFamily.base')};
+ font-size: ${dt('fonts.fontSize.300')};
+ font-weight: ${dt('fonts.fontWeight.regular')};
+}
+
+/* ─── Скрываем нативный time picker (заменён кастомным в footer) ─── */
+.p-datepicker-time-picker.p-datepicker-time-picker:not(.p-datepicker-time-picker-custom) {
+ display: none;
+}
+
+.p-datepicker-buttonbar.p-datepicker-buttonbar .p-button.p-button {
+ background: transparent;
+ color: ${dt('button.colorScheme.light.text.primary.color')};
+ border: 0 none;
+ font-family: ${dt('fonts.fontFamily.heading')};
+ font-weight: ${dt('button.root.label.fontWeight')};
+ transition: background-color ${dt('button.root.transitionDuration')};
+}
+
+.p-datepicker-buttonbar.p-datepicker-buttonbar .p-button.p-button:not(:disabled):hover {
+ background: ${dt('button.colorScheme.light.text.primary.hoverBackground')};
+ color: ${dt('button.colorScheme.light.text.primary.color')};
+}
+
+.p-datepicker-buttonbar.p-datepicker-buttonbar .p-button.p-button:not(:disabled):active {
+ background: ${dt('button.colorScheme.light.text.primary.activeBackground')};
+ color: ${dt('button.colorScheme.light.text.primary.color')};
+}
+
+/* ─── Clear icon: скрываем при пустом значении ─── */
+.p-datepicker.p-datepicker:not(.p-inputwrapper-filled) .p-datepicker-clear-icon,
+.p-datepicker.p-datepicker:has(.p-datepicker-input:placeholder-shown) .p-datepicker-clear-icon {
+ display: none;
+}
+
+/* ─── Custom time picker (InputNumber без кнопок) ─── */
+.p-datepicker-time-picker-custom.p-datepicker-time-picker-custom {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: ${dt('datepicker.timePicker.gap')};
+ padding: ${dt('datepicker.timePicker.padding')};
+ border-top: ${dt('form.borderWidth')} solid ${dt('datepicker.panel.borderColor')};
+}
+
+.p-datepicker-time-picker-custom .p-datepicker-time-field {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: ${dt('datepicker.timePicker.buttonGap')};
+ width: auto;
+}
+
+.p-datepicker-time-picker-custom .p-datepicker-time-label {
+ font-family: ${dt('fonts.fontFamily.heading')};
+ font-size: ${dt('fonts.fontSize.100')};
+ font-weight: ${dt('fonts.fontWeight.regular')};
+ line-height: 1;
+ color: ${dt('datepicker.extend.extTimePicker.color')};
+}
+
+.p-datepicker-time-picker-custom .p-inputnumber,
+.p-datepicker-time-picker-custom .p-datepicker-time-input.p-datepicker-time-input {
+ width: ${dt('datepicker.extend.extTimePicker.minWidth')};
+ text-align: center;
+}
+
+.p-datepicker-time-picker-custom .p-datepicker-separator {
+ font-family: ${dt('fonts.fontFamily.heading')};
+ font-size: ${dt('fonts.fontSize.100')};
+ font-weight: ${dt('fonts.fontWeight.regular')};
+ line-height: 1;
+ color: ${dt('datepicker.extend.extTimePicker.color')};
+ margin-top: calc(${dt('fonts.fontSize.100')} + ${dt('datepicker.timePicker.buttonGap')});
+}
+
+:is(.p-datepicker-month-select, .p-datepicker-year-select) .p-select-dropdown {
+ color: ${dt('datepicker.inputIcon.color')};
+}
+
+:is(.p-datepicker-month-select, .p-datepicker-year-select) .p-select-label {
+ width: ${dt('sizing.24x')};
+}
+
+`;
diff --git a/src/prime-preset/tokens/tokens.json b/src/prime-preset/tokens/tokens.json
index 0df719aa..89c2186e 100644
--- a/src/prime-preset/tokens/tokens.json
+++ b/src/prime-preset/tokens/tokens.json
@@ -542,7 +542,15 @@
"800": "{sizing.16x}",
"900": "{sizing.20x}"
},
- "width": "{sizing.68x}",
+ "width": {
+ "100": "{sizing.6x}",
+ "200": "{sizing.8x}",
+ "300": "{sizing.10x}",
+ "350": "{sizing.11x}",
+ "400": "{sizing.12x}",
+ "500": "{sizing.60x}",
+ "full": "{sizing.max}"
+ },
"gap": {
"100": "{spacing.1x}",
"200": "{spacing.2x}",
diff --git a/src/stories/components/date-picker/date-picker.stories.ts b/src/stories/components/date-picker/date-picker.stories.ts
new file mode 100644
index 00000000..d9304de2
--- /dev/null
+++ b/src/stories/components/date-picker/date-picker.stories.ts
@@ -0,0 +1,469 @@
+import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
+import { DatePickerComponent } from '../../../lib/components/date-picker/date-picker.component';
+import { DatePickerBasicComponent } from './examples/date-picker-basic.component';
+import { DatePickerRangeComponent } from './examples/date-picker-range.component';
+import { DatePickerTimeComponent } from './examples/date-picker-time.component';
+import { DatePickerButtonBarComponent } from './examples/date-picker-button-bar.component';
+import { DatePickerInlineComponent } from './examples/date-picker-inline.component';
+import { DatePickerDisabledComponent } from './examples/date-picker-disabled.component';
+import { DatePickerInvalidComponent } from './examples/date-picker-invalid.component';
+import { DatePickerClearIconComponent } from './examples/date-picker-clear-icon.component';
+
+type DatePickerArgs = DatePickerComponent & {};
+
+const meta: Meta = {
+ title: 'Components/Form/DatePicker',
+ component: DatePickerComponent,
+ tags: ['autodocs'],
+ decorators: [
+ moduleMetadata({
+ imports: [
+ DatePickerComponent,
+ DatePickerBasicComponent,
+ DatePickerRangeComponent,
+ DatePickerTimeComponent,
+ DatePickerButtonBarComponent,
+ DatePickerInlineComponent,
+ DatePickerDisabledComponent,
+ DatePickerInvalidComponent,
+ DatePickerClearIconComponent,
+ ],
+ }),
+ ],
+ parameters: {
+ docs: {
+ description: {
+ component: `Компонент выбора даты и времени. [Figma Design](https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=484-5726).
+
+\`\`\`typescript
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+\`\`\``,
+ },
+ },
+ designTokens: {
+ prefix: '--p-datepicker',
+ },
+ },
+ argTypes: {
+ size: {
+ control: 'select',
+ options: ['small', 'medium', 'large', 'xlarge'],
+ description: 'Размер поля ввода',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'medium' },
+ type: { summary: "'small' | 'medium' | 'large' | 'xlarge'" },
+ },
+ },
+ selectionMode: {
+ control: 'select',
+ options: ['single', 'multiple', 'range'],
+ description: 'Режим выбора даты',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'single' },
+ type: { summary: "'single' | 'multiple' | 'range'" },
+ },
+ },
+ showIcon: {
+ control: 'boolean',
+ description: 'Отображает иконку календаря',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'true' },
+ },
+ },
+ showClear: {
+ control: 'boolean',
+ description: 'Отображает иконку очистки содержимого поля',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ showButtonBar: {
+ control: 'boolean',
+ description: 'Отображает кнопки «Сегодня» и «Очистить» в подвале панели',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ showTime: {
+ control: 'boolean',
+ description: 'Включает выбор времени',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ inline: {
+ control: 'boolean',
+ description: 'Отображает календарь инлайн без поля ввода',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ invalid: {
+ control: 'boolean',
+ description: 'Флаг невалидности поля (меняет цвет границ)',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ disabled: {
+ control: 'boolean',
+ description: 'Отключает возможность ввода в поле',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ readonly: {
+ control: 'boolean',
+ description: 'Переводит поле в режим «только чтение»',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'false' },
+ },
+ },
+ placeholder: {
+ control: 'text',
+ description: 'Текст подсказки внутри поля',
+ table: {
+ category: 'Props',
+ },
+ },
+ dateFormat: {
+ control: 'text',
+ description: 'Формат отображения даты',
+ table: {
+ category: 'Props',
+ defaultValue: { summary: 'dd.mm.yy' },
+ },
+ },
+ onSelect: {
+ control: false,
+ description: 'Событие выбора даты',
+ table: {
+ category: 'Events',
+ type: { summary: 'EventEmitter' },
+ },
+ },
+ valueChange: {
+ control: false,
+ description: 'Событие изменения значения',
+ table: {
+ category: 'Events',
+ type: { summary: 'EventEmitter' },
+ },
+ },
+ },
+ args: {
+ size: 'medium',
+ selectionMode: 'single',
+ showIcon: true,
+ showClear: false,
+ showButtonBar: false,
+ showTime: false,
+ inline: false,
+ invalid: false,
+ disabled: false,
+ readonly: false,
+ placeholder: 'Выберите дату доставки',
+ dateFormat: 'dd.mm.yy',
+ },
+};
+
+const commonTemplate = `
+
+`;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ name: 'Default',
+ render: (args) => {
+ const parts: string[] = [];
+
+ if (args.size && args.size !== 'medium') parts.push(`size="${args.size}"`);
+ if (args.selectionMode && args.selectionMode !== 'single') parts.push(`selectionMode="${args.selectionMode}"`);
+ if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`);
+ if (args.dateFormat && args.dateFormat !== 'dd.mm.yy') parts.push(`dateFormat="${args.dateFormat}"`);
+ if (!args.showIcon) parts.push(`[showIcon]="false"`);
+ if (args.showClear) parts.push(`[showClear]="true"`);
+ if (args.showButtonBar) parts.push(`[showButtonBar]="true"`);
+ if (args.showTime) parts.push(`[showTime]="true"`);
+ if (args.inline) parts.push(`[inline]="true"`);
+ if (args.invalid) parts.push(`[invalid]="true"`);
+ if (args.disabled) parts.push(`[disabled]="true"`);
+ if (args.readonly) parts.push(`[readonly]="true"`);
+
+ const template = parts.length
+ ? ``
+ : ``;
+
+ return { props: args, template };
+ },
+ args: { placeholder: 'Выберите дату доставки' },
+ parameters: {
+ docs: {
+ description: {
+ story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.',
+ },
+ },
+ },
+};
+
+export const Range: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { selectionMode: 'range', placeholder: 'Период доставки' },
+ parameters: {
+ docs: {
+ description: { story: 'Выбор диапазона дат для указания периода доставки.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-range',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerRangeComponent {
+ dates: Date[] | null = null;
+}
+ `,
+ },
+ },
+ },
+};
+
+export const Time: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { showTime: true, placeholder: 'Дата и время отправки' },
+ parameters: {
+ docs: {
+ description: { story: 'Выбор даты и времени для указания точного момента отправки.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-time',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerTimeComponent {
+ date: Date | null = null;
+}
+ `,
+ },
+ },
+ },
+};
+
+export const ButtonBar: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { showButtonBar: true, placeholder: 'Дата отгрузки' },
+ parameters: {
+ docs: {
+ description: { story: 'Панель кнопок «Сегодня» и «Очистить» для быстрого выбора.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-button-bar',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerButtonBarComponent {
+ date: Date | null = null;
+}
+ `,
+ },
+ },
+ },
+};
+
+export const Inline: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { inline: true, placeholder: undefined },
+ parameters: {
+ docs: {
+ description: { story: 'Инлайн-календарь без поля ввода.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-inline',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerInlineComponent {
+ date: Date | null = null;
+}
+ `,
+ },
+ },
+ },
+};
+
+export const Disabled: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { disabled: true, placeholder: 'Дата заблокирована' },
+ parameters: {
+ docs: {
+ description: { story: 'Заблокированное поле выбора даты.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-disabled',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerDisabledComponent {
+ date: Date | null = null;
+}
+ `,
+ },
+ },
+ },
+};
+
+export const Invalid: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { invalid: true, placeholder: 'Некорректная дата' },
+ parameters: {
+ docs: {
+ description: { story: 'Невалидное состояние поля выбора даты.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-invalid',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerInvalidComponent {
+ date: Date | null = null;
+}
+ `,
+ },
+ },
+ },
+};
+
+export const ClearIcon: Story = {
+ render: (args) => ({ props: args, template: commonTemplate }),
+ args: { showClear: true, placeholder: 'Дата с очисткой' },
+ parameters: {
+ docs: {
+ description: { story: 'Иконка очистки для быстрого сброса выбранной даты.' },
+ source: {
+ language: 'ts',
+ code: `
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '@cdek-it/angular-ui-kit';
+
+@Component({
+ selector: 'app-date-picker-clear-icon',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template: \\\`
+
+ \\\`,
+})
+export class DatePickerClearIconComponent {
+ date: Date | null = null;
+}
+ `,
+ },
+ },
+ },
+};
diff --git a/src/stories/components/date-picker/examples/date-picker-basic.component.ts b/src/stories/components/date-picker/examples/date-picker-basic.component.ts
new file mode 100644
index 00000000..e3c4b469
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-basic.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-basic',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerBasicComponent {
+ date: Date | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-button-bar.component.ts b/src/stories/components/date-picker/examples/date-picker-button-bar.component.ts
new file mode 100644
index 00000000..6b8d38ed
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-button-bar.component.ts
@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-button-bar',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerButtonBarComponent {
+ date: Date | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-clear-icon.component.ts b/src/stories/components/date-picker/examples/date-picker-clear-icon.component.ts
new file mode 100644
index 00000000..d80efe2b
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-clear-icon.component.ts
@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-clear-icon',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerClearIconComponent {
+ date: Date | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-disabled.component.ts b/src/stories/components/date-picker/examples/date-picker-disabled.component.ts
new file mode 100644
index 00000000..98a3ad3f
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-disabled.component.ts
@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-disabled',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerDisabledComponent {
+ date: Date | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-inline.component.ts b/src/stories/components/date-picker/examples/date-picker-inline.component.ts
new file mode 100644
index 00000000..ad5e1016
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-inline.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-inline',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerInlineComponent {
+ date: Date | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-invalid.component.ts b/src/stories/components/date-picker/examples/date-picker-invalid.component.ts
new file mode 100644
index 00000000..a376a097
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-invalid.component.ts
@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-invalid',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerInvalidComponent {
+ date: Date | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-range.component.ts b/src/stories/components/date-picker/examples/date-picker-range.component.ts
new file mode 100644
index 00000000..e3553862
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-range.component.ts
@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-range',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerRangeComponent {
+ dates: Date[] | null = null;
+}
diff --git a/src/stories/components/date-picker/examples/date-picker-time.component.ts b/src/stories/components/date-picker/examples/date-picker-time.component.ts
new file mode 100644
index 00000000..3ad5c0b1
--- /dev/null
+++ b/src/stories/components/date-picker/examples/date-picker-time.component.ts
@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { DatePickerComponent } from '../../../../lib/components/date-picker/date-picker.component';
+
+const template = `
+
+`;
+
+@Component({
+ selector: 'app-date-picker-time',
+ standalone: true,
+ imports: [DatePickerComponent],
+ template,
+})
+export class DatePickerTimeComponent {
+ date: Date | null = null;
+}