Skip to content

Commit 0c0462d

Browse files
authored
Stop ToggleSwitch jumping when the loading spinner is shown (#4152)
1 parent 2d8c704 commit 0c0462d

3 files changed

Lines changed: 64 additions & 0 deletions

File tree

.changeset/hungry-poems-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/view-components": patch
3+
---
4+
5+
Reserve height as well as width in `.ToggleSwitch-statusIcon` so the switch doesn't jump when the loading spinner is shown

app/components/primer/alpha/toggle_switch.pcss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,13 @@
184184

185185
.ToggleSwitch-statusIcon {
186186
display: flex;
187+
flex: none;
188+
align-items: center;
189+
justify-content: center;
187190
width: var(--base-size-16);
191+
height: var(--base-size-16);
192+
/* stylelint-disable-next-line primer/typography */
193+
line-height: 0;
188194
/* stylelint-disable-next-line primer/spacing */
189195
margin-top: 0.063rem;
190196
}

test/system/alpha/toggle_switch_test.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,61 @@ def test_fetch_made_without_turbo
112112
assert_equal "*/*", Primer::ViewComponents::ToggleSwitchController.last_request.headers["HTTP_ACCEPT"]
113113
end
114114

115+
def test_switch_does_not_move_when_loading_spinner_is_shown
116+
visit_preview(:small)
117+
118+
before_top = track_top
119+
120+
# Show the spinner the same way setLoadingState() does, without racing the request.
121+
unhide(find("[data-target='toggle-switch.loadingSpinner']", visible: :hidden))
122+
assert_selector("[data-target='toggle-switch.loadingSpinner']")
123+
124+
assert_in_delta before_top, track_top, 0.5
125+
end
126+
127+
def test_switch_does_not_move_when_error_icon_is_shown
128+
visit_preview(:small)
129+
130+
before_top = track_top
131+
132+
# Show the error icon the same way setErrorState() does.
133+
unhide(find("[data-target='toggle-switch.errorIcon']", visible: :hidden))
134+
assert_selector("[data-target='toggle-switch.errorIcon']")
135+
136+
assert_in_delta before_top, track_top, 0.5
137+
end
138+
139+
def test_loading_spinner_is_centered_in_the_status_icon_slot
140+
visit_preview(:small)
141+
142+
unhide(find("[data-target='toggle-switch.loadingSpinner']", visible: :hidden))
143+
assert_selector("[data-target='toggle-switch.loadingSpinner']")
144+
145+
assert_in_delta vertical_center(find("[data-target='toggle-switch.loadingSpinner'] svg")),
146+
vertical_center(find(".ToggleSwitch-statusIcon")),
147+
0.5
148+
end
149+
115150
private
116151

152+
# The status icons are hidden with the `hidden` attribute. `hidden` is an HTMLElement IDL
153+
# property, so `el.hidden = false` silently does nothing on the error icon's <svg>. The
154+
# component removes the attribute, so do the same here.
155+
def unhide(element)
156+
execute_script("arguments[0].removeAttribute('hidden');", element)
157+
end
158+
159+
def track_top
160+
evaluate_script("arguments[0].getBoundingClientRect().top;", find(".ToggleSwitch-track"))
161+
end
162+
163+
def vertical_center(element)
164+
evaluate_script(
165+
"(function (r) { return r.top + r.height / 2; })(arguments[0].getBoundingClientRect());",
166+
element
167+
)
168+
end
169+
117170
def wait_for_spinner
118171
refute_selector("[data-target='toggle-switch.loadingSpinner']")
119172
end

0 commit comments

Comments
 (0)