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

Add trailing visuals to the text field #3237

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/new-students-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/view-components": patch
---

Add trailing visuals to the text field
61 changes: 60 additions & 1 deletion app/components/primer/alpha/text_field.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@
** .FormControl
** ├─ .FormControl-label
** │ ├─ .FormControl-input-wrap
** │ │ ├─ .FormControl-input-trailingVisualWrap
** │ │ │ ├─ .FormControl-input-trailingVisual
** │ │ ├─ .FormControl-input-leadingVisualWrap
** │ │ │ ├─ .FormControl-input-leadingVisual
** │ │ ├─ .FormControl-input
Expand Down Expand Up @@ -253,6 +255,21 @@
}
}

& .FormControl-input-trailingVisualWrap {
position: absolute;
top: var(--base-size-8);
right: var(--base-size-8);
display: flex;
height: var(--base-size-16);
align-items: center;
gap: var(--base-size-4);
color: var(--fgColor-muted);
pointer-events: none;
&:has( .FormControl-input-trailingVisualText) {
max-width: 25%;
padding-left: var(--base-size-8);
}
}
/* TODO: replace with new Button component */
& .FormControl-input-trailingAction {
position: absolute;
Expand Down Expand Up @@ -333,10 +350,29 @@
}
}

/* if trailingVisual is present */

/*
┌──────────────────┬──32px──┐
╎ ┌──────────────┐ ┌────┐ ╎
╎ 24px 24px ╎
╎ 24px 24px ╎
╎ └──────────────┘ └────┘ ╎
└──────────────────┴────────┘
*/

&.FormControl-input-wrap--trailingVisual {
& .FormControl-input {
padding-inline-end: calc(var(--control-medium-paddingInline-condensed) + var(--base-size-16) + var(--control-medium-gap));
}
&:has(.FormControl-input-trailingVisualText) .FormControl-input {
padding-inline-end: 25%
}
}

/*
┌──────────────────┬──32px──┐
╎ ┌──────────────┐ ┌────┐ ╎
╎ 24px 24px ╎
╎ └──────────────┘ └────┘ ╎
└──────────────────┴────────┘
*/
Expand Down Expand Up @@ -377,6 +413,10 @@
top: calc(var(--control-medium-paddingInline-condensed) - var(--base-size-2)); /* 6px */
left: calc(var(--control-medium-paddingInline-condensed) - var(--base-size-2)); /* 6px */
}
& .FormControl-input-trailingVisualWrap {
top: calc(var(--control-medium-paddingInline-condensed) - var(--base-size-2)); /* 6px */
right: calc(var(--control-medium-paddingInline-condensed) - var(--base-size-2)); /* 6px */
}

/*
┌──────────────────┬──28px──┐
Expand Down Expand Up @@ -427,6 +467,10 @@
top: var(--control-medium-paddingInline-normal);
left: var(--control-medium-paddingInline-normal);
}
& .FormControl-input-trailingVisualWrap {
top: var(--control-medium-paddingInline-normal);
right: var(--control-medium-paddingInline-normal);
}

/*
┌──36px──┬───12px padding──────┐
Expand All @@ -444,6 +488,21 @@
}
}

&.FormControl-input-wrap--trailingVisual {
& .FormControl-input {
padding-inline-end: calc(var(--control-large-paddingInline-normal) + var(--base-size-16) + var(--control-large-gap));
}
&:has(.FormControl-input-trailingVisualText) .FormControl-input {
padding-inline-end: 25%
}
}

&.FormControl-input-wrap--trailingText {
& .FormControl-input.FormControl-large {
padding-inline-end: 25%;
}
}

/*
┌──────────────────┬──36px──┐
╎ ┌──────────────┐ ┌────┐ ╎
Expand Down
17 changes: 10 additions & 7 deletions app/lib/primer/forms/dsl/text_field_input.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# frozen_string_literal: true

module Primer
module Forms
module Dsl
# :nodoc:
class TextFieldInput < Input
attr_reader(
*%i[
name label show_clear_button leading_visual leading_spinner clear_button_id
name label show_clear_button leading_visual leading_spinner trailing_visual clear_button_id
visually_hide_label inset monospace field_wrap_classes auto_check_src
]
)
Expand All @@ -20,6 +18,7 @@ def initialize(name:, label:, **system_arguments)

@show_clear_button = system_arguments.delete(:show_clear_button)
@leading_visual = system_arguments.delete(:leading_visual)
@trailing_visual = system_arguments.delete(:trailing_visual)
@leading_spinner = !!system_arguments.delete(:leading_spinner)
@clear_button_id = system_arguments.delete(:clear_button_id)
@inset = system_arguments.delete(:inset)
Expand Down Expand Up @@ -48,6 +47,14 @@ def initialize(name:, label:, **system_arguments)
alias inset? inset
alias monospace? monospace

def trailing_visual?
!!@trailing_visual
end

def leading_visual?
!!@leading_visual
end

def to_component
TextField.new(input: self)
end
Expand All @@ -60,10 +67,6 @@ def focusable?
true
end

def leading_visual?
!!@leading_visual
end

def validation_arguments
if auto_check_src.present?
super.merge(
Expand Down
5 changes: 5 additions & 0 deletions app/lib/primer/forms/text_field.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,10 @@
<%= render(Primer::Beta::Octicon.new(icon: :"x-circle-fill")) %>
</button>
<% end %>
<% if @input.trailing_visual %>
<div class="FormControl-input-trailingVisualWrap">
<%= render(trailing_visual_component) %>
</div>
<% end %>
<% end %>
<% end %>
45 changes: 41 additions & 4 deletions app/lib/primer/forms/text_field.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# frozen_string_literal: true

module Primer
module Forms
# :nodoc:
class TextField < BaseComponent
delegate :builder, :form, to: :@input

Expand All @@ -24,9 +21,9 @@ def initialize(input:)
"FormControl-input-wrap",
INPUT_WRAP_SIZE[input.size],
"FormControl-input-wrap--trailingAction": @input.show_clear_button?,
"FormControl-input-wrap--trailingVisual": @input.trailing_visual?,
"FormControl-input-wrap--leadingVisual": @input.leading_visual?
),

hidden: @input.hidden?
}
end
Expand All @@ -41,6 +38,46 @@ def auto_check_authenticity_token
)
end
end

def trailing_visual_component
return @trailing_visual_component if defined?(@trailing_visual_component)
visual = @input.trailing_visual

# Render icon if specified
@trailing_visual_component = if (icon_arguments = visual[:icon])
icon_arguments[:classes] = class_names(
icon_arguments.delete(:classes),
"FormControl-input-trailingVisualIcon"
)

Primer::Beta::Octicon.new(**icon_arguments)
elsif (label_arguments = visual[:label])
# Render label if specified
label_arguments[:classes] = class_names(
label_arguments.delete(:classes),
"FormControl-input-trailingVisualLabel"
)

text = label_arguments.delete(:text)
Primer::Beta::Label.new(**label_arguments).with_content(text)
elsif (counter_arguments = visual[:counter])
# Render counter if specified
counter_arguments[:classes] = class_names(
counter_arguments.delete(:classes),
"FormControl-input-trailingVisualCounter"
)

Primer::Beta::Counter.new(**counter_arguments)
elsif (truncate_arguments = visual[:text])
# Render text if specified
truncate_arguments[:classes] = class_names(
truncate_arguments.delete(:classes),
"FormControl-input-trailingVisualText"
)
text = truncate_arguments.delete(:text)
Primer::Beta::Truncate.new(**truncate_arguments).with_content(text)
end
end
end
end
end
30 changes: 30 additions & 0 deletions previews/primer/alpha/text_field_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,36 @@ def monospace
render(Primer::Alpha::TextField.new(monospace: true, name: "my-text-field", label: "My text field"))
end

# @label With trailing icon
# @snapshot
def with_trailing_icon
render(Primer::Alpha::TextField.new(trailing_visual: { icon: { icon: :search } }, name: "my-text-field", label: "My text field"))
end

# @label With trailing text
# @snapshot
def with_trailing_text
render(Primer::Alpha::TextField.new( trailing_visual: { text: { text: "minute" } }, name: "my-text-field", label: "My text field"))
end

# @label With trailing long text
# @snapshot
def with_trailing_long_text
render(Primer::Alpha::TextField.new( trailing_visual: { text: { text: "Long trailing text" } }, name: "my-text-field", label: "My text field"))
end

# @label With trailing counter
# @snapshot
def with_trailing_counter
render(Primer::Alpha::TextField.new( trailing_visual: { counter: { counter: 5 } }, name: "my-text-field", label: "My text field"))
end

# @label With trailing label
# @snapshot
def with_trailing_label
render(Primer::Alpha::TextField.new( trailing_visual: { label: { text: "Hello" } }, name: "my-text-field", label: "My text field"))
end

# @label With leading visual
# @snapshot
def with_leading_visual
Expand Down
32 changes: 32 additions & 0 deletions test/components/alpha/text_field_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,36 @@ def test_enforces_leading_visual_when_spinner_requested

assert_includes error.message, "must also specify a leading visual"
end

def test_renders_a_trailing_visual_icon
render_inline(Primer::Alpha::TextField.new(**@default_params, trailing_visual: { icon: :search }))

assert_selector ".FormControl-input-trailingVisualWrap" do
assert_selector "svg.octicon.octicon-search.FormControl-input-trailingVisualIcon"
end
end

def test_renders_a_trailing_visual_text
render_inline(Primer::Alpha::TextField.new(**@default_params, trailing_visual: { text: 'minute' }))

assert_selector ".FormControl-input-trailingVisualWrap" do
assert_selector ".FormControl-input-trailingVisualText", text: "minute"
end
end

def test_renders_a_trailing_visual_label
render_inline(Primer::Alpha::TextField.new(**@default_params, trailing_visual: { label: 'Hello' }))

assert_selector ".FormControl-input-trailingVisualWrap" do
assert_selector ".FormControl-input-trailingVisualLabel.Label", text: "Hello"
end
end

def test_renders_a_trailing_visual_Counter
render_inline(Primer::Alpha::TextField.new(**@default_params, trailing_visual: { counter: '5' }))

assert_selector ".FormControl-input-trailingVisualWrap" do
assert_selector ".FormControl-input-trailingVisualCounter.Counter", text: "5"
end
end
end