From 56445a9770353135ad965adccab3cee07e1941ee Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:09:09 +0200 Subject: [PATCH] Add DatePicker component and dsl for single_date_picker and range_date_picker form fields --- .../basic-range-date-picker.component.html | 1 + .../basic-range-date-picker.component.ts | 3 + .../basic-single-date-picker.component.ts | 4 +- .../datepicker/styles/datepicker.modal.sass | 6 +- .../open_project/forms/date_picker.html.erb | 23 ++++++++ lib/primer/open_project/forms/date_picker.rb | 17 ++++++ .../open_project/forms/dsl/input_methods.rb | 30 ++++++---- .../forms/dsl/range_date_picker_input.rb | 21 +++++++ .../forms/dsl/single_date_picker_input.rb | 33 +++++++++++ .../open_project/common/datepicker_preview.rb | 12 ++-- .../common/datepicker_preview/range.html.erb | 59 ++++++++++++++++++- .../common/datepicker_preview/single.html.erb | 59 ++++++++++++++++++- 12 files changed, 249 insertions(+), 19 deletions(-) create mode 100644 lib/primer/open_project/forms/date_picker.html.erb create mode 100644 lib/primer/open_project/forms/date_picker.rb create mode 100644 lib/primer/open_project/forms/dsl/range_date_picker_input.rb create mode 100644 lib/primer/open_project/forms/dsl/single_date_picker_input.rb diff --git a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html index 6c9a4f8699d3..cc5f6d13f9bf 100644 --- a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html +++ b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html @@ -8,6 +8,7 @@ [attr.data-value]="value" [id]="id" [name]="name" + [attr.name]="name" [required]="required" [disabled]="disabled" [ngModel]="stringValue" diff --git a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts index 35f9f3494f69..699eef974892 100644 --- a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts @@ -115,6 +115,8 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce @Input() inputClassNames = ''; + @Input() inDialog = false; + @ViewChild('input') input:ElementRef; stringValue = ''; @@ -199,6 +201,7 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce !!this.minimalDate && dayElem.dateObj <= this.minimalDate, ); }, + static: this.inDialog, }, this.input.nativeElement as HTMLInputElement, ); diff --git a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts index 130ab9e59b10..6ebc84272fd7 100644 --- a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts @@ -52,7 +52,6 @@ import { DayElement } from 'flatpickr/dist/types/instance'; import { populateInputsFromDataset } from '../../dataset-inputs'; import { DeviceService } from 'core-app/core/browser/device.service'; - @Component({ selector: 'op-basic-single-date-picker', templateUrl: './basic-single-date-picker.component.html', @@ -96,6 +95,8 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O @Input() remoteFieldKey = null; + @Input() inDialog = false; + @ViewChild('input') input:ElementRef; mobile = false; @@ -179,6 +180,7 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O !!this.minimalDate && dayElem.dateObj <= this.minimalDate, ); }, + static: this.inDialog, }, this.input.nativeElement as HTMLInputElement, ); diff --git a/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass b/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass index 35d68546881f..904eb15044cd 100644 --- a/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass +++ b/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass @@ -1,6 +1,5 @@ @import '../../app/spot/styles/sass/variables' @import '../../global_styles/openproject/variables' - .op-datepicker-modal display: flex flex-direction: column @@ -88,3 +87,8 @@ &--date-form &:only-child grid-column: 1 / 3 + +.flatpickr-wrapper + // Make flatpickr behave correctly when it is instantiated + // inside a dialog using the static: true option. + width: 100% diff --git a/lib/primer/open_project/forms/date_picker.html.erb b/lib/primer/open_project/forms/date_picker.html.erb new file mode 100644 index 000000000000..04599700e834 --- /dev/null +++ b/lib/primer/open_project/forms/date_picker.html.erb @@ -0,0 +1,23 @@ +<%= render(FormControl.new(input: @input, tag: :"primer-datepicker-field")) do %> + <%= content_tag(:div, **@field_wrap_arguments) do %> + <%# leading spinner implies a leading visual %> + <% if @input.leading_visual || @input.leading_spinner? %> + + <%= render(Primer::Beta::Octicon.new(**@input.leading_visual, data: { target: "primer-text-field.leadingVisual" })) %> + <% if @input.leading_spinner? %> + <%= render(Primer::Beta::Spinner.new(size: :small, hidden: true, data: { target: "primer-text-field.leadingSpinner" })) %> + <% end %> + + <% end %> + <%= render Primer::ConditionalWrapper.new(condition: @input.auto_check_src, tag: "auto-check", csrf: auto_check_authenticity_token, src: @input.auto_check_src) do %> + <%= angular_component_tag @datepicker_options.fetch(:component), + inputs: @datepicker_options.merge( + id: @datepicker_options.fetch(:id) { builder.field_id(@input.name) }, + name: @datepicker_options.fetch(:name) { builder.field_name(@input.name) }, + value: @datepicker_options.fetch(:value) { @input.input_arguments[:value] }, + inputClassNames: @datepicker_options.fetch(:class) { @input.input_arguments[:class] } + ) + %> + <% end %> + <% end %> +<% end %> diff --git a/lib/primer/open_project/forms/date_picker.rb b/lib/primer/open_project/forms/date_picker.rb new file mode 100644 index 000000000000..f16834892046 --- /dev/null +++ b/lib/primer/open_project/forms/date_picker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + # :nodoc: + class DatePicker < Primer::Forms::TextField + include AngularHelper + + def initialize(input:, datepicker_options:) + super(input:) + @datepicker_options = datepicker_options + end + end + end + end +end diff --git a/lib/primer/open_project/forms/dsl/input_methods.rb b/lib/primer/open_project/forms/dsl/input_methods.rb index e2151a300dd5..eca21f89ef75 100644 --- a/lib/primer/open_project/forms/dsl/input_methods.rb +++ b/lib/primer/open_project/forms/dsl/input_methods.rb @@ -6,31 +6,39 @@ module Forms module Dsl module InputMethods def autocompleter(**, &) - add_input AutocompleterInput.new(builder: @builder, form: @form, **, &) + add_input AutocompleterInput.new(builder:, form:, **, &) end - def work_package_autocompleter(**, &) - add_input WorkPackageAutocompleterInput.new(builder: @builder, form: @form, **, &) + def color_select_list(**, &) + add_input ColorSelectInput.new(builder:, form:, **, &) + end + + def html_content(**, &) + add_input HtmlContentInput.new(builder:, form:, **, &) end def project_autocompleter(**, &) - add_input ProjectAutocompleterInput.new(builder: @builder, form: @form, **, &) + add_input ProjectAutocompleterInput.new(builder:, form:, **, &) + end + + def range_date_picker(**) + add_input RangeDatePickerInput.new(builder:, form:, **) end def rich_text_area(**) - add_input RichTextAreaInput.new(builder: @builder, form: @form, **) + add_input RichTextAreaInput.new(builder:, form:, **) end - def storage_manual_project_folder_selection(**) - add_input StorageManualProjectFolderSelectionInput.new(builder: @builder, form: @form, **) + def single_date_picker(**) + add_input SingleDatePickerInput.new(builder:, form:, **) end - def color_select_list(**, &) - add_input ColorSelectInput.new(builder:, form:, **, &) + def storage_manual_project_folder_selection(**) + add_input StorageManualProjectFolderSelectionInput.new(builder:, form:, **) end - def html_content(**, &) - add_input HtmlContentInput.new(builder: @builder, form: @form, **, &) + def work_package_autocompleter(**, &) + add_input WorkPackageAutocompleterInput.new(builder:, form:, **, &) end end end diff --git a/lib/primer/open_project/forms/dsl/range_date_picker_input.rb b/lib/primer/open_project/forms/dsl/range_date_picker_input.rb new file mode 100644 index 000000000000..e3a5710228de --- /dev/null +++ b/lib/primer/open_project/forms/dsl/range_date_picker_input.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + module Dsl + class RangeDatePickerInput < SingleDatePickerInput + def derive_datepicker_options(options) + options.reverse_merge( + component: "opce-range-date-picker" + ) + end + + def type + :range_date_picker + end + end + end + end + end +end diff --git a/lib/primer/open_project/forms/dsl/single_date_picker_input.rb b/lib/primer/open_project/forms/dsl/single_date_picker_input.rb new file mode 100644 index 000000000000..d62eed45b220 --- /dev/null +++ b/lib/primer/open_project/forms/dsl/single_date_picker_input.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + module Dsl + class SingleDatePickerInput < Primer::Forms::Dsl::TextFieldInput + attr_reader :datepicker_options + + def initialize(name:, label:, datepicker_options:, **system_arguments) + @datepicker_options = derive_datepicker_options(datepicker_options) + + super(name:, label:, **system_arguments) + end + + def derive_datepicker_options(options) + options.reverse_merge( + component: "opce-single-date-picker" + ) + end + + def to_component + DatePicker.new(input: self, datepicker_options:) + end + + def type + :single_date_picker + end + end + end + end + end +end diff --git a/lookbook/previews/open_project/common/datepicker_preview.rb b/lookbook/previews/open_project/common/datepicker_preview.rb index c9bdeaca41ef..4359c5d1dc24 100644 --- a/lookbook/previews/open_project/common/datepicker_preview.rb +++ b/lookbook/previews/open_project/common/datepicker_preview.rb @@ -28,8 +28,10 @@ class DatepickerPreview < Lookbook::Preview # before using or contributing to date pickers. # # @param value date - def single(value: Time.zone.today.iso8601) - render_with_template(locals: { value: }) + # @param in_dialog toggle + # @param icon [Symbol] octicon + def single(value: Time.zone.today, in_dialog: false, icon: :calendar) + render_with_template(locals: { value:, in_dialog:, icon: }) end ## @@ -48,8 +50,10 @@ def single(value: Time.zone.today.iso8601) # before using or contributing to date pickers. # # @param value text - def range(value: "#{Time.zone.today.iso8601} - #{Time.zone.today.iso8601}") - render_with_template(locals: { value: }) + # @param in_dialog toggle + # @param icon [Symbol] octicon + def range(value: "#{Time.zone.today.iso8601} - #{Time.zone.today.iso8601}", in_dialog: false, icon: :calendar) + render_with_template(locals: { value:, in_dialog:, icon: }) end end end diff --git a/lookbook/previews/open_project/common/datepicker_preview/range.html.erb b/lookbook/previews/open_project/common/datepicker_preview/range.html.erb index 559e35bba55a..ca2e4295e87e 100644 --- a/lookbook/previews/open_project/common/datepicker_preview/range.html.erb +++ b/lookbook/previews/open_project/common/datepicker_preview/range.html.erb @@ -1 +1,58 @@ -<%= tag :'opce-range-date-picker', value: %> +<% + the_form = Class.new(ApplicationForm) do + form do |query_form| + query_form.range_date_picker( + name: :date, + label: 'Date', + leading_visual: { icon: }, + value: value, + datepicker_options: { inDialog: in_dialog } + ) + + query_form.range_date_picker( + name: :date, + label: 'Date', + leading_visual: { icon: }, + value: value, + datepicker_options: { inDialog: in_dialog } + ) + + query_form.range_date_picker( + name: :date, + label: 'Date', + leading_visual: { icon: }, + value: value, + datepicker_options: { inDialog: in_dialog } + ) + end + end +%> + +<% if in_dialog %> + <%= render(Primer::Alpha::Dialog.new(title: "Dialog Title", + size: :large, + open: true, + id: "my-dialog")) do |d| %> + <% d.with_show_button { "Show dialog" } %> + <% d.with_header(variant: :medium) %> + + <%= render(Primer::Alpha::Dialog::Body.new) do + primer_form_with( + url: '/abc', + id: "my-form") do |f| + render(the_form.new(f)) + end + end %> + + <%= d.with_footer do %> + <%= render(Primer::Beta::Button.new(data: { "close-dialog-id": "my-dialog" })) { I18n.t(:button_cancel) } %> + <%= render(Primer::Beta::Button.new(scheme: :primary, type: :submit, form: "my-form")) { I18n.t(:button_apply) } %> + <% end %> + <% end %> +<% else %> + <%= primer_form_with( + url: '/abc', + id: "my-form") do |f| + render(the_form.new(f)) + end %> +<% end %> diff --git a/lookbook/previews/open_project/common/datepicker_preview/single.html.erb b/lookbook/previews/open_project/common/datepicker_preview/single.html.erb index bd6afbdcdc15..8f5f34ef2765 100644 --- a/lookbook/previews/open_project/common/datepicker_preview/single.html.erb +++ b/lookbook/previews/open_project/common/datepicker_preview/single.html.erb @@ -1 +1,58 @@ -<%= tag :'opce-single-date-picker', value: %> +<% + the_form = Class.new(ApplicationForm) do + form do |query_form| + query_form.single_date_picker( + name: :date, + label: 'Date', + leading_visual: { icon: }, + value: value.iso8601, + datepicker_options: { inDialog: in_dialog } + ) + + query_form.single_date_picker( + name: :date, + label: 'Date', + leading_visual: { icon: }, + value: value.iso8601, + datepicker_options: { inDialog: in_dialog } + ) + + query_form.single_date_picker( + name: :date, + label: 'Date', + leading_visual: { icon: }, + value: value.iso8601, + datepicker_options: { inDialog: in_dialog } + ) + end + end +%> + +<% if in_dialog %> + <%= render(Primer::Alpha::Dialog.new(title: "Dialog Title", + size: :large, + open: true, + id: "my-dialog")) do |d| %> + <% d.with_show_button { "Show dialog" } %> + <% d.with_header(variant: :medium) %> + + <%= render(Primer::Alpha::Dialog::Body.new) do + primer_form_with( + url: '/abc', + id: "my-form") do |f| + render(the_form.new(f)) + end + end %> + + <%= d.with_footer do %> + <%= render(Primer::Beta::Button.new(data: { "close-dialog-id": "my-dialog" })) { I18n.t(:button_cancel) } %> + <%= render(Primer::Beta::Button.new(scheme: :primary, type: :submit, form: "my-form")) { I18n.t(:button_apply) } %> + <% end %> + <% end %> +<% else %> + <%= primer_form_with( + url: '/abc', + id: "my-form") do |f| + render(the_form.new(f)) + end %> +<% end %>