diff --git a/.changeset/added-width-to-inputs.md b/.changeset/added-width-to-inputs.md new file mode 100644 index 0000000000..949e894265 --- /dev/null +++ b/.changeset/added-width-to-inputs.md @@ -0,0 +1,10 @@ +--- +'@openproject/primer-view-components': minor +--- + +Added a "width" attribute to control how wide an input is rendered on screen. + +It is active for: + +- TextField +- Select diff --git a/.gitignore b/.gitignore index f48032404c..3505ad1055 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ tmp/ # Simplecov folder coverage/ demo/public/assets/ + +# IDE folders +.idea diff --git a/app/components/primer/alpha/text_field.pcss b/app/components/primer/alpha/text_field.pcss index 49d622d36a..cd17e81eb3 100644 --- a/app/components/primer/alpha/text_field.pcss +++ b/app/components/primer/alpha/text_field.pcss @@ -216,11 +216,40 @@ gap: var(--base-size-16); } +/* widths */ +@define-mixin FormControl-input-width { + &.FormControl-input-width--auto { + width: auto; + } + + &.FormControl-input-width--small { + width: min(256px, 100vw - 2rem); + } + + &.FormControl-input-width--medium { + width: min(320px, 100vw - 2rem); + } + + &.FormControl-input-width--large { + width: min(480px, 100vw - 2rem); + } + + &.FormControl-input-width--xlarge { + width: min(640px, 100vw - 2rem); + } + + &.FormControl-input-width--xxlarge { + width: min(960px, 100vw - 2rem); + } +} + /* positioning for leading/trailing items for TextInput */ .FormControl-input-wrap { position: relative; display: grid; + @mixin FormControl-input-width; + & .FormControl-input-leadingVisualWrap { position: absolute; top: var(--base-size-8); @@ -479,6 +508,8 @@ display: grid; grid-template-columns: minmax(0, auto) var(--base-size-16); + @mixin FormControl-input-width; + /* mask allows for background-color to respect themes */ &::after { width: var(--base-size-16); diff --git a/app/forms/custom_width_fields_form.rb b/app/forms/custom_width_fields_form.rb new file mode 100644 index 0000000000..f3897c16f4 --- /dev/null +++ b/app/forms/custom_width_fields_form.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# :nodoc: +class CustomWidthFieldsForm < ApplicationForm + form do |f| + f.text_field( + name: :ultimate_answer, + label: "Ultimate answer", + required: true, + caption: "The answer to life, the universe, and everything", + input_width: :medium + ) + + f.select_list( + name: "cities", + label: "Cool cities", + caption: "Select your favorite!", + include_blank: true, + input_width: :small + ) do |city_list| + city_list.option(label: "Lopez Island", value: "lopez_island") + city_list.option(label: "Bellevue", value: "bellevue") + city_list.option(label: "Seattle", value: "seattle") + end + end +end diff --git a/lib/primer/forms/dsl/input.rb b/lib/primer/forms/dsl/input.rb index d357b5f7aa..c81d55cfa3 100644 --- a/lib/primer/forms/dsl/input.rb +++ b/lib/primer/forms/dsl/input.rb @@ -45,6 +45,17 @@ class Input }.freeze SIZE_OPTIONS = SIZE_MAPPINGS.keys + DEFAULT_INPUT_WIDTH = :auto + INPUT_WIDTH_MAPPINGS = { + DEFAULT_INPUT_WIDTH => "FormControl-input-width--auto", + :small => "FormControl-input-width--small", + :medium => "FormControl-input-width--medium", + :large => "FormControl-input-width--large", + :xlarge => "FormControl-input-width--xlarge", + :xxlarge => "FormControl-input-width--xxlarge" + }.freeze + INPUT_WIDTH_OPTIONS = INPUT_WIDTH_MAPPINGS.keys + include Primer::ClassNameHelper attr_reader :builder, :form, :input_arguments, :label_arguments, :caption, :validation_message, :ids, :form_control, :base_id @@ -73,6 +84,7 @@ def initialize(builder:, form:, **system_arguments) @invalid = @input_arguments.delete(:invalid) @full_width = @input_arguments.delete(:full_width) @size = @input_arguments.delete(:size) + @input_width = @input_arguments.delete(:input_width) # If scope_name_to_model is false, the name of the input for eg. `my_field` # will be `my_field` instead of the Rails default of `model[my_field]`. @@ -152,6 +164,7 @@ def add_input_data(key, value) def remove_input_data(key) input_data.delete(key) end + # :nocov: def merge_input_arguments!(arguments) @@ -226,6 +239,10 @@ def size @size ||= SIZE_MAPPINGS.include?(@size) ? @size : DEFAULT_SIZE end + def input_width + @input_width ||= INPUT_WIDTH_MAPPINGS.include?(@input_width) ? @input_width : DEFAULT_INPUT_WIDTH + end + def validation_messages @validation_messages ||= if validation_message @@ -257,6 +274,7 @@ def type def to_component raise_for_abstract_method!(__method__) end + # :nocov: def focusable? @@ -309,6 +327,7 @@ def aria_join(*values) def raise_for_abstract_method!(method_name) raise NotImplementedError, "subclasses must implement ##{method_name}." end + # :nocov: end end diff --git a/lib/primer/forms/select.rb b/lib/primer/forms/select.rb index 6c76a789f1..5efcdeaf1d 100644 --- a/lib/primer/forms/select.rb +++ b/lib/primer/forms/select.rb @@ -13,8 +13,11 @@ def initialize(input:) Primer::Forms::Dsl::Input::SIZE_MAPPINGS[@input.size] ) + wrap_classes = ["FormControl-select-wrap"] + wrap_classes << Primer::Forms::Dsl::Input::INPUT_WIDTH_MAPPINGS[@input.input_width] if @input.input_width + @field_wrap_arguments = { - class: "FormControl-select-wrap", + class: class_names(wrap_classes), hidden: @input.hidden? } end diff --git a/lib/primer/forms/text_field.rb b/lib/primer/forms/text_field.rb index 1840f92434..789dbcdd9e 100644 --- a/lib/primer/forms/text_field.rb +++ b/lib/primer/forms/text_field.rb @@ -19,14 +19,16 @@ def initialize(input:) Primer::Forms::Dsl::Input::SIZE_MAPPINGS[@input.size] ) - @field_wrap_arguments = { - class: class_names( - "FormControl-input-wrap", - INPUT_WRAP_SIZE[input.size], - "FormControl-input-wrap--trailingAction": @input.show_clear_button?, - "FormControl-input-wrap--leadingVisual": @input.leading_visual? - ), + wrap_classes = [ + "FormControl-input-wrap", + INPUT_WRAP_SIZE[input.size], + { "FormControl-input-wrap--trailingAction": @input.show_clear_button? }, + { "FormControl-input-wrap--leadingVisual": @input.leading_visual? } + ] + wrap_classes << Primer::Forms::Dsl::Input::INPUT_WIDTH_MAPPINGS[@input.input_width] if @input.input_width + @field_wrap_arguments = { + class: class_names(wrap_classes), hidden: @input.hidden? } end diff --git a/previews/primer/alpha/select_preview.rb b/previews/primer/alpha/select_preview.rb index 99f289b834..8efa3092a2 100644 --- a/previews/primer/alpha/select_preview.rb +++ b/previews/primer/alpha/select_preview.rb @@ -17,6 +17,7 @@ class SelectPreview < ViewComponent::Preview # @param disabled toggle # @param invalid toggle # @param validation_message text + # @param input_width [Symbol] select [auto, small, medium, large, xlarge, xxlarge] def playground( name: "my-select-list", id: "my-select-list", @@ -28,7 +29,8 @@ def playground( full_width: false, disabled: false, invalid: false, - validation_message: nil + validation_message: nil, + input_width: nil ) system_arguments = { name: name, @@ -41,7 +43,8 @@ def playground( full_width: full_width, disabled: disabled, invalid: invalid, - validation_message: validation_message + validation_message: validation_message, + input_width: input_width } render(Primer::Alpha::Select.new(**system_arguments)) do |component| diff --git a/previews/primer/alpha/text_field_preview.rb b/previews/primer/alpha/text_field_preview.rb index 7fd6836bda..6640542a55 100644 --- a/previews/primer/alpha/text_field_preview.rb +++ b/previews/primer/alpha/text_field_preview.rb @@ -23,6 +23,7 @@ class TextFieldPreview < ViewComponent::Preview # @param inset toggle # @param monospace toggle # @param leading_visual_icon octicon + # @param input_width [Symbol] select [auto, small, medium, large, xlarge, xxlarge] def playground( name: "my-text-field", id: "my-text-field", @@ -40,7 +41,8 @@ def playground( placeholder: nil, inset: false, monospace: false, - leading_visual_icon: nil + leading_visual_icon: nil, + input_width: nil ) system_arguments = { name: name, @@ -58,7 +60,8 @@ def playground( validation_message: validation_message, placeholder: placeholder, inset: inset, - monospace: monospace + monospace: monospace, + input_width: input_width } if leading_visual_icon diff --git a/previews/primer/forms_preview.rb b/previews/primer/forms_preview.rb index e326710c04..b6b29feeb0 100644 --- a/previews/primer/forms_preview.rb +++ b/previews/primer/forms_preview.rb @@ -7,6 +7,8 @@ def single_text_field_form; end def multi_text_field_form; end + def custom_width_fields_form; end + def text_field_and_checkbox_form; end def horizontal_form; end diff --git a/previews/primer/forms_preview/custom_width_fields_form.html.erb b/previews/primer/forms_preview/custom_width_fields_form.html.erb new file mode 100644 index 0000000000..28faebf35b --- /dev/null +++ b/previews/primer/forms_preview/custom_width_fields_form.html.erb @@ -0,0 +1,3 @@ +<%= primer_form_with(url: "/foo") do |f| %> + <%= render(CustomWidthFieldsForm.new(f)) %> +<% end %> diff --git a/test/lib/primer/forms/text_field_input_test.rb b/test/lib/primer/forms/text_field_input_test.rb index 51c624eece..22d293b883 100644 --- a/test/lib/primer/forms/text_field_input_test.rb +++ b/test/lib/primer/forms/text_field_input_test.rb @@ -18,6 +18,18 @@ def test_hidden_text_field assert_selector "input[type=text]#foo", visible: :hidden end + def test_medium_width_text_field + render_in_view_context do + primer_form_with(url: "/foo") do |f| + render_inline_form(f) do |text_field_form| + text_field_form.text_field(name: :foo, label: "Foo", input_width: :medium) + end + end + end + + assert_selector "div.FormControl-input-width--medium" + end + def test_no_error_markup model = DeepThought.new(41) model.valid? # populate validation error messages diff --git a/test/lib/primer/forms_test.rb b/test/lib/primer/forms_test.rb index 15548fa597..3010dd013a 100644 --- a/test/lib/primer/forms_test.rb +++ b/test/lib/primer/forms_test.rb @@ -6,6 +6,13 @@ class Primer::FormsTest < Minitest::Test include Primer::ComponentTestHelpers + def test_custom_width_fields + render_inline Primer::FormTestComponent.new(form_class: CustomWidthFieldsForm) + + assert_selector "div.FormControl-input-width--medium" + assert_selector "div.FormControl-input-width--small" + end + def test_renders_correct_form_structure render_preview :single_text_field_form