-
Notifications
You must be signed in to change notification settings - Fork 5
feat(date-time-field): new tedi-ready component #507 #522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rc
Are you sure you want to change the base?
Changes from all commits
de306b7
e4792d9
da59e61
2c393cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| <tedi-modal class="tedi-date-time-field-modal"> | ||
| <tedi-modal-header> | ||
| <h2>{{ "date-time-field.modal-title" | tediTranslate }}</h2> | ||
| </tedi-modal-header> | ||
| <tedi-modal-content | ||
| class="tedi-date-time-field-modal__content" | ||
| [class.tedi-date-time-field-modal__content--range]="isRange" | ||
| > | ||
| <tedi-calendar | ||
| #calendar | ||
| class="tedi-date-time-field-modal__calendar" | ||
| [bordered]="false" | ||
| [value]="draft()" | ||
| [currentMonth]="month()" | ||
| [mode]="data.mode" | ||
| [selectionLevel]="data.selectionLevel" | ||
| [localeCode]="data.localeCode" | ||
| [showOutsideDays]="data.showOutsideDays" | ||
| [showWeekNumbers]="data.showWeekNumbers" | ||
| [numberOfMonths]="data.numberOfMonths" | ||
| [monthYearSelectType]="data.monthYearSelectType" | ||
| [required]="data.required" | ||
| [disabledMatchers]="data.disabledMatchers" | ||
| [availableDays]="data.availableDays" | ||
| [unavailableDays]="data.unavailableDays" | ||
| [shouldDisableMonth]="data.shouldDisableMonth" | ||
| [shouldDisableYear]="data.shouldDisableYear" | ||
| (currentMonthChange)="month.set($event)" | ||
| (select)="handleCalendarSelect()" | ||
| /> | ||
|
|
||
| <div class="tedi-date-time-field-modal__separator" aria-hidden="true"> | ||
| <span class="tedi-date-time-field-modal__separator-line"></span> | ||
| </div> | ||
|
|
||
| @if (isRange) { | ||
| <div class="tedi-date-time-field-modal__times"> | ||
| <ng-container | ||
| [ngTemplateOutlet]="rangeTimeTpl" | ||
| [ngTemplateOutletContext]="{ | ||
| kind: 'from', | ||
| time: fromTime(), | ||
| slots: fromSlots(), | ||
| heading: data.timeHeading ?? ('date-time-field.time-heading-from' | tediTranslate), | ||
| dateLabel: fromDateLabel(), | ||
| }" | ||
| /> | ||
| <ng-container | ||
| [ngTemplateOutlet]="rangeTimeTpl" | ||
| [ngTemplateOutletContext]="{ | ||
| kind: 'to', | ||
| time: toTime(), | ||
| slots: toSlots(), | ||
| heading: data.timeHeading ?? ('date-time-field.time-heading-to' | tediTranslate), | ||
| dateLabel: toDateLabel(), | ||
| }" | ||
| /> | ||
| </div> | ||
| } @else { | ||
| <div class="tedi-date-time-field-modal__time"> | ||
| <span class="tedi-date-time-field-modal__heading"> | ||
| {{ data.timeHeading ?? ("date-time-field.time-heading" | tediTranslate) }} | ||
| </span> | ||
| @if (!singleSlots()?.length && useNativeTimeInput()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Inconsistent native time input condition between single and range modes. Single mode (line 64) gates native time input on both Also applies to: 116-116 🤖 Prompt for AI Agents |
||
| <tedi-form-field | ||
| class="tedi-date-time-field-modal__native-time" | ||
| [size]="data.size" | ||
| > | ||
| <tedi-time-field | ||
| inputId="dtf-modal-time" | ||
| [useNativePicker]="true" | ||
| [clearable]="false" | ||
| [value]="singleTime()" | ||
| (valueChange)="handleTimeSelect($event)" | ||
| /> | ||
| </tedi-form-field> | ||
| } @else { | ||
| <tedi-time-picker | ||
| [value]="singleTime()" | ||
| [variant]="singleSlots()?.length ? 'slots' : 'scroll'" | ||
| [timeSlots]="singleSlots() ?? []" | ||
| [showSlotIndicator]="data.gridVariant === 'radio'" | ||
| [columns]="data.slotColumns" | ||
| [minuteStep]="data.minuteStep" | ||
| (valueChange)="handleTimeSelect($event)" | ||
| /> | ||
| } | ||
| </div> | ||
| } | ||
| </tedi-modal-content> | ||
| <tedi-modal-footer> | ||
| <button tedi-button type="button" variant="secondary" (click)="cancel()"> | ||
| {{ "date-field.cancel" | tediTranslate }} | ||
| </button> | ||
| <button tedi-button type="button" (click)="confirm()"> | ||
| {{ "date-field.confirm" | tediTranslate }} | ||
| </button> | ||
| </tedi-modal-footer> | ||
| </tedi-modal> | ||
|
|
||
| <ng-template | ||
| #rangeTimeTpl | ||
| let-kind="kind" | ||
| let-time="time" | ||
| let-slots="slots" | ||
| let-heading="heading" | ||
| let-dateLabel="dateLabel" | ||
| > | ||
| <div class="tedi-date-time-field-modal__time"> | ||
| <div class="tedi-date-time-field-modal__time-header"> | ||
| <span class="tedi-date-time-field-modal__heading">{{ heading }}</span> | ||
| @if (dateLabel) { | ||
| <span class="tedi-date-time-field-modal__date">{{ dateLabel }}</span> | ||
| } | ||
| </div> | ||
| @if (!slots?.length) { | ||
| <tedi-form-field | ||
| class="tedi-date-time-field-modal__native-time" | ||
| [size]="data.size" | ||
| > | ||
| <tedi-time-field | ||
| [inputId]="'dtf-modal-time-' + kind" | ||
| [useNativePicker]="true" | ||
| [clearable]="false" | ||
| [value]="time" | ||
| (valueChange)="handleRangeTimeSelect(kind, $event)" | ||
| /> | ||
| </tedi-form-field> | ||
| } @else { | ||
| <tedi-time-picker | ||
| [value]="time" | ||
| variant="slots" | ||
| [timeSlots]="slots" | ||
| [showSlotIndicator]="data.gridVariant === 'radio'" | ||
| [columns]="data.slotColumns" | ||
| [minuteStep]="data.minuteStep" | ||
| (valueChange)="handleRangeTimeSelect(kind, $event)" | ||
| /> | ||
| } | ||
| </div> | ||
| </ng-template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| @use "@tedi-design-system/core/bootstrap-utility/breakpoints"; | ||
|
|
||
| .tedi-date-time-field-modal { | ||
| --_tedi-modal-body-padding: 0; | ||
|
|
||
| // Calendar and time picker sit side-by-side on wider screens and stack (time | ||
| // below calendar) once the row no longer fits — the modal itself shrink-wraps | ||
| // this content (see `width: "fit-content"` in date-time-field.component.ts). | ||
| // | ||
| // `.tedi-modal-dialog .tedi-modal-content` (modal.component.scss) forces a | ||
| // column layout with a large gap; the selector below (ancestor + compound | ||
| // classes) outweighs it so the row layout and the tight, separator-driven | ||
| // spacing win. | ||
| .tedi-date-time-field-modal__content.tedi-modal-content { | ||
| flex-direction: row; | ||
| gap: 0; | ||
| align-items: stretch; | ||
|
|
||
| @include breakpoints.media-breakpoint-down(md) { | ||
| flex-direction: column; | ||
| align-items: center; | ||
| } | ||
| } | ||
|
|
||
| // Range: stack the calendar over the two ends. Side-mounting the two time | ||
| // pickers left the calendar with a tall empty gap and overflowed into a | ||
| // horizontal scroll on tablet, and the calendar↔time link was lost. Stacking | ||
| // keeps the ends grouped as a set beneath the shared calendar. | ||
| .tedi-date-time-field-modal__content--range.tedi-modal-content { | ||
| flex-direction: column; | ||
| align-items: center; | ||
| } | ||
|
|
||
| &__calendar { | ||
| flex-shrink: 0; | ||
| } | ||
|
|
||
| // The separator only makes sense while the calendar and time picker sit | ||
| // side-by-side. Once they stack, hide it — a horizontal line plus its padding | ||
| // just adds dead space between the two. | ||
| &__separator { | ||
| display: flex; | ||
| flex-shrink: 0; | ||
| align-items: stretch; | ||
| padding: var(--card-padding-md-default) var(--separator-spacing-x-01); | ||
|
|
||
| @include breakpoints.media-breakpoint-down(md) { | ||
| display: none; | ||
| } | ||
| } | ||
|
|
||
| &__separator-line { | ||
| width: var(--tedi-borders-01); | ||
| background: var(--general-separator-primary); | ||
| } | ||
|
|
||
| // The vertical separator belongs to the single side-by-side layout; range | ||
| // stacks vertically, so the two ends carry their own top divider instead. | ||
| &__content--range &__separator { | ||
| display: none; | ||
| } | ||
|
|
||
| &__times { | ||
| display: flex; | ||
| gap: var(--layout-grid-gutters-04); | ||
| justify-content: center; | ||
|
|
||
| @include breakpoints.media-breakpoint-down(md) { | ||
| flex-direction: column; | ||
| align-items: center; | ||
| width: 100%; | ||
| } | ||
| } | ||
|
|
||
| &__content--range &__times { | ||
| align-self: stretch; | ||
| padding-top: var(--card-padding-md-default); | ||
| border-top: var(--tedi-borders-01) solid var(--general-separator-primary); | ||
| } | ||
|
|
||
| // Groups the heading with its start/end date so they read as one label, and | ||
| // owns the gap down to the picker (same spacing whether or not a date shows). | ||
| &__time-header { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-self: stretch; | ||
| padding-top: var(--card-padding-md-default); | ||
| padding-bottom: var(--card-padding-xs); | ||
| } | ||
|
|
||
| &__time-header &__heading { | ||
| padding: 0; | ||
| } | ||
|
|
||
| // Echoes the start/end date under each heading so it stays clear which | ||
| // calendar end the picker below drives. | ||
| &__date { | ||
| font-size: var(--body-small-regular-size); | ||
| color: var(--general-text-secondary); | ||
| text-align: center; | ||
| } | ||
|
|
||
| &__time { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| padding: 0 var(--card-padding-md-default) var(--card-padding-md-default); | ||
| } | ||
|
|
||
| &__native-time { | ||
| width: 100%; | ||
| max-width: var(--tedi-containers-01); | ||
| } | ||
|
|
||
| &__heading { | ||
| align-self: stretch; | ||
| padding-top: var(--card-padding-md-default); | ||
| padding-bottom: var(--card-padding-xs); | ||
| font-size: var(--body-bold-size); | ||
| font-weight: var(--body-bold-weight); | ||
| line-height: var(--body-bold-line-height); | ||
| color: var(--general-text-primary); | ||
| text-align: center; | ||
| } | ||
|
|
||
| // See date-time-field.component.scss: re-assert the slot grid's `display: grid` (which | ||
| // tedi-radio-card-group's flex can override by injection order) and drop its own | ||
| // padding since `&__time` already provides it. | ||
| .tedi-time-picker__grid { | ||
| display: grid; | ||
| padding: 0; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Document the actual
DateTimeFieldValuecontract.The component and stories use the exported
DateTimeFieldValuetype, but the documentation advertisesDateRange, which can imply the calendar-only range shape. Use the public alias and explicitly describe thefrom/tovalues as date-times.🤖 Prompt for AI Agents