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; +}