Skip to content
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

Optimizing the life cycle dialog #17502

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion app/forms/projects/life_cycles/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def base_input_attributes
label: "#{icon} #{text}".html_safe, # rubocop:disable Rails/OutputSafety
leading_visual: { icon: :calendar },
datepicker_options: {
inDialog: true,
inDialog: ProjectLifeCycles::Sections::EditDialogComponent::DIALOG_ID,
data: { action: "change->overview--project-life-cycles-form#handleChange" }
},
wrapper_data_attributes: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
[ngModel]="stringValue"
(ngModelChange)="changeValueFromInputDebounced($event)"
(focus)="showDatePicker()"
(click)="sentCalendarToTopLayer()"
/>
<ng-container *ngIf="mobile">
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce

@Input() inputClassNames = '';

@Input() inDialog = false;
@Input() inDialog:string;

@Input() dataAction = '';

Expand Down Expand Up @@ -170,7 +170,10 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce
}

showDatePicker():void {
this.datePickerInstance?.show();
if (!this.datePickerInstance?.isOpen) {
this.datePickerInstance?.show();
this.sentCalendarToTopLayer();
}
}

private initializeDatePicker() {
Expand All @@ -195,6 +198,9 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce

this.cdRef.detectChanges();
},
onOpen: () => {
this.sentCalendarToTopLayer();
},
onDayCreate: async (dObj:Date[], dStr:string, fp:flatpickr.Instance, dayElem:DayElement) => {
onDayCreate(
dayElem,
Expand All @@ -203,7 +209,8 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce
!!this.minimalDate && dayElem.dateObj <= this.minimalDate,
);
},
static: this.inDialog,
static: false,
appendTo: this.appendToBodyOrDialog(),
},
this.input.nativeElement as HTMLInputElement,
);
Expand Down Expand Up @@ -234,4 +241,31 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce
private resolveDateArrayToString(dates:string[]):string {
return dates.join(` ${rangeSeparator} `);
}

// In case the date picker is opened in a dialog, it needs to be in the top layer
// since the dialog is also there. This method is called in two cases:
// 1. When the date picker is opened
// 2. When the date picker is already opened and the user clicks on the input
// The later is necessary as otherwise the date picker would be removed from the top layer
// when the user clicks on the input. That is because the input is not part of the date picker
// so clicking on it would be considered a click on the backdrop which removes an item from
// the top layer again.
public sentCalendarToTopLayer() {
if (!this.datePickerInstance.isOpen || !this.inDialog) {
return;
}

const calendarContainer = this.datePickerInstance.datepickerInstance.calendarContainer;
calendarContainer.setAttribute('popover', '');
calendarContainer.showPopover();
calendarContainer.style.marginTop = '0';
}

private appendToBodyOrDialog():HTMLElement|undefined {
if (this.inDialog) {
return document.querySelector(`#${this.inDialog}`) as HTMLElement;
}

return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
[disabled]="disabled"
(input)="changeValueFromInput($event.target.value)"
(focus)="showDatePicker()"
(click)="sentCalendarToTopLayer()"
[attr.data-remote-field-key]="remoteFieldKey"
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O

@Input() remoteFieldKey = null;

@Input() inDialog = false;
@Input() inDialog:string;

@Input() dataAction = '';

Expand Down Expand Up @@ -146,7 +146,10 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O
}

showDatePicker():void {
this.datePickerInstance?.show();
if (!this.datePickerInstance?.isOpen) {
this.datePickerInstance?.show();
this.sentCalendarToTopLayer();
}
}

private initializeDatePicker() {
Expand Down Expand Up @@ -174,6 +177,9 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O

this.cdRef.detectChanges();
},
onOpen: () => {
this.sentCalendarToTopLayer();
},
onDayCreate: async (_dObj:Date[], _dStr:string, _fp:flatpickr.Instance, dayElem:DayElement) => {
onDayCreate(
dayElem,
Expand All @@ -182,7 +188,8 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O
!!this.minimalDate && dayElem.dateObj <= this.minimalDate,
);
},
static: this.inDialog,
static: false,
appendTo: this.appendToBodyOrDialog(),
},
this.input.nativeElement as HTMLInputElement,
);
Expand All @@ -204,4 +211,31 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O
registerOnTouched(fn:(_:string) => void):void {
this.onTouched = fn;
}

// In case the date picker is opened in a dialog, it needs to be in the top layer
// since the dialog is also there. This method is called in two cases:
// 1. When the date picker is opened
// 2. When the date picker is already opened and the user clicks on the input
// The later is necessary as otherwise the date picker would be removed from the top layer
// when the user clicks on the input. That is because the input is not part of the date picker
// so clicking on it would be considered a click on the backdrop which removes an item from
// the top layer again.
public sentCalendarToTopLayer() {
if (!this.datePickerInstance.isOpen || !this.inDialog) {
return;
}

const calendarContainer = this.datePickerInstance.datepickerInstance.calendarContainer;
calendarContainer.setAttribute('popover', '');
calendarContainer.showPopover();
calendarContainer.style.marginTop = '0';
}

private appendToBodyOrDialog():HTMLElement|undefined {
if (this.inDialog) {
return document.querySelector(`#${this.inDialog}`) as HTMLElement;
}

return undefined;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<%=
render(Primer::Alpha::Dialog.new(title: t("label_life_cycle_step_plural"),
size: :large,
id: dialog_id)) do |d|
size: :medium_portrait,
id: DIALOG_ID)) do |d|
d.with_header(variant: :large)
d.with_body(classes: "Overlay-body_autocomplete_height") do
d.with_body do
render(::ProjectLifeCycles::Sections::EditComponent.new(model))
end
d.with_footer do
component_collection do |footer_collection|
footer_collection.with_component(Primer::ButtonComponent.new(
data: {
"close-dialog-id": dialog_id
"close-dialog-id": DIALOG_ID
}
)) do
t("button_cancel")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
Expand Down Expand Up @@ -33,9 +35,7 @@ class EditDialogComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def dialog_id
"edit-project-life-cycles-dialog"
end
DIALOG_ID = "edit-project-life-cycles-dialog"
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ def within_async_content(close_after_yield: false, &)
end

def set_date_for(step, value:)
dialog_selector = "##{::ProjectLifeCycles::Sections::EditDialogComponent::DIALOG_ID}"

datepicker = if value.is_a?(Array)
Components::RangeDatepicker.new
Components::RangeDatepicker.new(dialog_selector)
else
Components::BasicDatepicker.new
Components::BasicDatepicker.new(dialog_selector)
end

datepicker.open(
Expand Down
Loading