From fdd7bc1ea3309a649586a04b3a10eeef5e1d0650 Mon Sep 17 00:00:00 2001 From: Cameron Dutro Date: Tue, 17 Jan 2023 22:31:14 -0800 Subject: [PATCH] Toggle switch error messages (#1760) Co-authored-by: Neal Lindsay Co-authored-by: camertron Co-authored-by: Katie Langerman <18661030+langermank@users.noreply.github.com> --- .changeset/swift-jokes-sparkle.md | 5 +++ .eslintrc.json | 3 +- .../primer/alpha/toggle_switch/default.png | Bin 2255 -> 2252 bytes .../primer/alpha/toggle_switch/focused.png | Bin 2583 -> 2566 bytes app/components/primer/alpha/text_field.pcss | 6 +++ .../primer/alpha/toggle_switch.html.erb | 6 ++- .../primer/alpha/toggle_switch.pcss | 6 +++ app/components/primer/alpha/toggle_switch.ts | 38 +++++++++++++----- app/components/primer/primer.ts | 1 + app/forms/example_toggle_switch_form.rb | 2 +- .../controllers/toggle_switch_controller.rb | 2 +- lib/primer/forms/toggle_switch.html.erb | 9 ++++- lib/primer/forms/toggle_switch.rb | 6 +-- lib/primer/forms/toggle_switch_input.ts | 19 +++++++++ .../example_toggle_switch_form.html.erb | 4 +- test/css/component_specific_selectors_test.rb | 3 +- .../primer/forms/integration_forms_test.rb | 28 +++++++++++++ 17 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 .changeset/swift-jokes-sparkle.md create mode 100644 lib/primer/forms/toggle_switch_input.ts diff --git a/.changeset/swift-jokes-sparkle.md b/.changeset/swift-jokes-sparkle.md new file mode 100644 index 0000000000..c445115eeb --- /dev/null +++ b/.changeset/swift-jokes-sparkle.md @@ -0,0 +1,5 @@ +--- +'@primer/view-components': patch +--- + +Show error messages when toggle switches fail diff --git a/.eslintrc.json b/.eslintrc.json index ce61d6a752..7957655d7a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -62,7 +62,8 @@ ], "custom-elements/tag-name-matches-class": [ "error", {"suffix": "Element"} - ] + ], + "i18n-text/no-en": "off" } } ] diff --git a/.playwright/screenshots/previews.test.ts-snapshots/primer/alpha/toggle_switch/default.png b/.playwright/screenshots/previews.test.ts-snapshots/primer/alpha/toggle_switch/default.png index 58103884146df6106dd9de3740189a1fa8d85958..b177d9159bc6c10f700b2f74d2a761dd845ab387 100644 GIT binary patch literal 2252 zcmcIld03L^7XNgtEVY`lrd;n-$`qDQ*=S}irCeghHB*ONZ%JBerl_doNYjiKEl#PV zqEKVDnJX!x5R^9!5r{D*7qr|66$vpEC7AE)xqsYy|GxL1_q^vl`+0xQIadPxk!EWw z)&KxthC1qV3;v?~E#$&xU1OmMqP@KusMn4#H5S%KmA) z{zeLRW@JG*5zdxsiH#0CC`+K}P2Y?GbRkr22Mj^#b$vAf;sZ|^{tcpUN&sVsesBLj z6^A+F389}xdB{e6mA7kuNmutwBCQjB+*&S2(^}zVmvgT=%*6ZgR#0qGU=Rt@KlAMg zzRHtwG(0Hwb88b?A}KDmw!W&S3k8Dl(2wIMHaO zWgdm6Is;-cNaRxU{2p00~(f1v^^G! z$Dcd1Z0UYS4pMx)EoE+WJ6wwK%%?u`_~% zDV?rE->&Su!O{&d6d~ujBj_1PYF1i(<0nuZcz6S7;XLX%-syn|Mu%%jV0K`#}^_YT@odj@J&f+1)qpG^l>cpDeh@phPqdb({4f>vrkIu%`t7M ztaNvB`gBmX1ymPbie>=2%dHqMNGl#cS zERwWgr4_h|=9uOG-!%!WaoiwbpeXDhU#rya9JrdSqA{PRG{6Bcx02|Mg zZMK^B0vO^ZepYwk+^n3d+z3H!-UZ&l%r~Q}7FcOA1I%>H;KW~<=_w|hxfArqEWlLszQ$s_;p%&G9 zQ!@z}X`>-mm2U>46EHDoBMw_TjZ9U~_)=^%51vd-wSHWxp)=vpneUzt={c7)l66ea z=&bn$t;EGfI5ZS7EYc5~9F~t!ncFYXM@Z}~Ki8uaG0JP#ZcJ*F%Bvmh9bm*)pQDdv zm`ZNn6p0dTtrA}pLI3uYh*6qy9#Az=9!i)k-dpRYz)~68Vg8hEw&y~`EMr;47z?I$ zE0_wFMzok4t7X{Qqjc}d1-;uqB!74{J3l{^CR)S|b2wg5Yd>6;8bFgnn=76(s29bL zFJ9Rwlgk+&K26sS{@z>NM$6S-B*GGIa9Q}Ckr91@Vp^dPjt{do`5J*{Uho7BkQwFb zg5ypjQnFyuJ-7)?`;pC>C@9;hM-yMg;BdIU8vKts{K?Tj#06IFWx)?D7|IUIU6d!T z;6L#x?)abOIe7I9m(TiluO&MM=}G(%Pi4WG{TA#rxv2Bm&1!L-5PyzUwYo4Q`TTOT zum3Fn?c29?%(%ovhr&X%W#+JQFuctDWL1XTLU6;{yMJ$5 zbT%Hp$7;aK_PN&77^3@^@8An1fahnSZw^l-{}CS7g)7QMRO{yb#-zgtKmX-V#@Or z2SgB4`I{Z(Ze=CwRugCfJS2gWt8BJ*zkfhj>*S-JVA)hesX0Pmt8vR+c|QIAePOI0 zF03j6f>5BD!TF2yB&IVm+BH;G=4kD4D?J%aE>g99CY~LPzBOJv)=A46%bWTJTCOqN z)dZ8o*4cpwQ6Gsq;}*!xF7%X8ix7RaGXG;c5Q0sdvmQ(29gZ`(J1gK}r^oUVc7<;Q z4GB}`7<6bDnRG>|FMCy8M`C1u9(aQ8EiQ(;ZI&ksySpdf$R05-EXH^3%bHFX->*43 zKy22m{K@=G0-nX&{A0b{`O~MJgL~rU*mYI`aZ=(ui-i-3LD#^Y?~MuU7Z)!f=U!a& zcJ2L-#Rje{OXs9|)jCLT(#Q+af4Eop->v=EyTN)$e%AN@A#Turh3?!PGKez&vfNP! Lf1lbTF~9r;x%Vfd literal 2255 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 - <%= render(Primer::Beta::Octicon.new(size: :small, color: :danger, icon: :alert, hidden: "true", data: { target: "toggle-switch.errorIcon" })) %> - <%= render(Primer::Beta::Spinner.new(size: :small, hidden: "true", data: { target: "toggle-switch.loadingSpinner" })) %> + + <%= render(Primer::Beta::Octicon.new(size: :small, color: :danger, icon: :alert, hidden: "true", data: { target: "toggle-switch.errorIcon" })) %> + <%= render(Primer::Beta::Spinner.new(size: :small, hidden: "true", data: { target: "toggle-switch.loadingSpinner" })) %> + <%= render(Primer::Beta::Text.new(aria: { hidden: true }, classes: "ToggleSwitch-status")) do %> <%= render(Primer::Box.new(classes: "ToggleSwitch-statusOn").with_content("On")) %> <%= render(Primer::Box.new(classes: "ToggleSwitch-statusOff").with_content("Off")) %> diff --git a/app/components/primer/alpha/toggle_switch.pcss b/app/components/primer/alpha/toggle_switch.pcss index 7b84b93b8c..18cfa4bc12 100644 --- a/app/components/primer/alpha/toggle_switch.pcss +++ b/app/components/primer/alpha/toggle_switch.pcss @@ -208,6 +208,12 @@ text-align: right; } +.ToggleSwitch-statusIcon { + width: var(--base-size-16, 16px); + display: flex; + margin-top: 0.063rem; +} + .ToggleSwitch--small { & .ToggleSwitch-status { font-size: var(--primer-text-body-size-small, 12px); diff --git a/app/components/primer/alpha/toggle_switch.ts b/app/components/primer/alpha/toggle_switch.ts index 53e050847c..c4b1792f61 100644 --- a/app/components/primer/alpha/toggle_switch.ts +++ b/app/components/primer/alpha/toggle_switch.ts @@ -97,10 +97,16 @@ class ToggleSwitchElement extends HTMLElement { } private setSuccessState(): void { + const event = new CustomEvent('toggleSwitchSuccess', {bubbles: true}) + this.dispatchEvent(event) + this.setFinishedState(false) } - private setErrorState(): void { + private setErrorState(message: string): void { + const event = new CustomEvent('toggleSwitchError', {bubbles: true, detail: message}) + this.dispatchEvent(event) + this.setFinishedState(true) } @@ -125,22 +131,32 @@ class ToggleSwitchElement extends HTMLElement { try { if (!this.src) throw new Error('invalid src') - const response = await fetch(this.src, { - credentials: 'same-origin', - method: 'POST', - headers: { - 'Requested-With': 'XMLHttpRequest' - }, - body - }) + + let response + + try { + response = await fetch(this.src, { + credentials: 'same-origin', + method: 'POST', + headers: { + 'Requested-With': 'XMLHttpRequest' + }, + body + }) + } catch (error) { + throw new Error('A network error occurred, please try again.') + } + if (response.ok) { this.setSuccessState() this.performToggle() } else { - this.setErrorState() + throw new Error(await response.text()) } } catch (error) { - this.setErrorState() + if (error instanceof Error) { + this.setErrorState(error.message || 'An error occurred, please try again.') + } } } } diff --git a/app/components/primer/primer.ts b/app/components/primer/primer.ts index 55fe5d633e..d3e190c4a1 100644 --- a/app/components/primer/primer.ts +++ b/app/components/primer/primer.ts @@ -13,3 +13,4 @@ import './alpha/tab_container' import './time_ago_component' import '../../../lib/primer/forms/primer_multi_input' import '../../../lib/primer/forms/primer_text_field' +import '../../../lib/primer/forms/toggle_switch_input' diff --git a/app/forms/example_toggle_switch_form.rb b/app/forms/example_toggle_switch_form.rb index 0636457d71..e92725b985 100644 --- a/app/forms/example_toggle_switch_form.rb +++ b/app/forms/example_toggle_switch_form.rb @@ -3,6 +3,6 @@ # :nodoc: class ExampleToggleSwitchForm < Primer::Forms::ToggleSwitchForm def initialize(**system_arguments) - super(name: :example_field, label: "Example", **system_arguments) + super(name: :example_field, label: "Example", caption: "This is an example toggle switch.", **system_arguments) end end diff --git a/demo/app/controllers/toggle_switch_controller.rb b/demo/app/controllers/toggle_switch_controller.rb index db0de26ca8..cb057a8bb7 100644 --- a/demo/app/controllers/toggle_switch_controller.rb +++ b/demo/app/controllers/toggle_switch_controller.rb @@ -37,7 +37,7 @@ def verify_artificial_authenticity_token # if provided, check token return if form_params[:authenticity_token] == "let_me_in" - head :unauthorized + render status: :unauthorized, plain: "Bad CSRF token" end def form_params diff --git a/lib/primer/forms/toggle_switch.html.erb b/lib/primer/forms/toggle_switch.html.erb index 1b49692a94..daba6bd438 100644 --- a/lib/primer/forms/toggle_switch.html.erb +++ b/lib/primer/forms/toggle_switch.html.erb @@ -1,9 +1,14 @@ -<%= content_tag(:div, **@form_group_arguments) do %> +<%= content_tag("toggle-switch-input", **@input.input_arguments) do %> <%= builder.label(@input.name, **@input.label_arguments) do %> <%= @input.label %> <% end %> - <%= render(Caption.new(input: @input)) %> + + <%= content_tag(:div, data: { target: "toggle-switch-input.validationElement" }, **@input.validation_arguments) do %> + <%= content_tag(:span, @input.validation_messages.first, data: { target: "toggle-switch-input.validationMessageElement" }, **@input.validation_message_arguments) %> + <% end %> + +
<%= render(Caption.new(input: @input)) %>
<% csrf = @input.csrf || @view_context.form_authenticity_token( diff --git a/lib/primer/forms/toggle_switch.rb b/lib/primer/forms/toggle_switch.rb index 6ff843dba1..68969cc45c 100644 --- a/lib/primer/forms/toggle_switch.rb +++ b/lib/primer/forms/toggle_switch.rb @@ -9,10 +9,8 @@ class ToggleSwitch < BaseComponent def initialize(input:) @input = input @input.add_label_classes("FormControl-label") - - @form_group_arguments = { class: "d-flex" } - - @form_group_arguments[:hidden] = "hidden" if @input.hidden? + @input.add_input_classes("FormControl-toggleSwitchInput") + @input.input_arguments[:hidden] = "hidden" if @input.hidden? end end end diff --git a/lib/primer/forms/toggle_switch_input.ts b/lib/primer/forms/toggle_switch_input.ts new file mode 100644 index 0000000000..45a58911c8 --- /dev/null +++ b/lib/primer/forms/toggle_switch_input.ts @@ -0,0 +1,19 @@ +import {controller, target} from '@github/catalyst' + +@controller +export class ToggleSwitchInputElement extends HTMLElement { + @target validationElement: HTMLElement + @target validationMessageElement: HTMLElement + + connectedCallback() { + this.addEventListener('toggleSwitchError', (event: Event) => { + this.validationMessageElement.innerText = (event as CustomEvent).detail + this.validationElement.removeAttribute('hidden') + }) + + this.addEventListener('toggleSwitchSuccess', () => { + this.validationMessageElement.innerText = '' + this.validationElement.setAttribute('hidden', 'hidden') + }) + } +} diff --git a/previews/primer/forms/forms_preview/example_toggle_switch_form.html.erb b/previews/primer/forms/forms_preview/example_toggle_switch_form.html.erb index f9a1aab130..cfc7c7b4dd 100644 --- a/previews/primer/forms/forms_preview/example_toggle_switch_form.html.erb +++ b/previews/primer/forms/forms_preview/example_toggle_switch_form.html.erb @@ -1 +1,3 @@ -<%= render(ExampleToggleSwitchForm.new(csrf: "let_me_in", src: toggle_switch_index_path)) %> +<%= render(ExampleToggleSwitchForm.new(csrf: "let_me_in", src: toggle_switch_index_path, id: "success-toggle")) %> +
+<%= render(ExampleToggleSwitchForm.new(csrf: "a_bad_value", src: toggle_switch_index_path, id: "error-toggle")) %> diff --git a/test/css/component_specific_selectors_test.rb b/test/css/component_specific_selectors_test.rb index e98d740496..4f9676ca8f 100644 --- a/test/css/component_specific_selectors_test.rb +++ b/test/css/component_specific_selectors_test.rb @@ -57,7 +57,8 @@ class ComponentSpecificSelectorsTest < Minitest::Test ".FormControl-input-wrap", ".FormControl-select-wrap", ".FormControl-checkbox-wrap", - ".FormControl-radio-wrap" + ".FormControl-radio-wrap", + ".FormControl-toggleSwitchInput" ], Primer::Alpha::ButtonMarketing => [ ".btn-mktg.disabled", diff --git a/test/lib/primer/forms/integration_forms_test.rb b/test/lib/primer/forms/integration_forms_test.rb index 6febb01518..d175fee046 100644 --- a/test/lib/primer/forms/integration_forms_test.rb +++ b/test/lib/primer/forms/integration_forms_test.rb @@ -39,5 +39,33 @@ def test_multi_submit assert result["country"], "CA" assert result["region"], "SK" end + + def test_toggle_switch_form_errors + visit_preview(:example_toggle_switch_form) + + find("#error-toggle toggle-switch").click + wait_for_toggle_switch_spinner + + assert_selector("#error-toggle [data-target='toggle-switch.errorIcon']") + assert_selector("#error-toggle", text: "Bad CSRF token") + + page.evaluate_script(<<~JAVASCRIPT) + document + .querySelector('#error-toggle toggle-switch') + .setAttribute('csrf', 'let_me_in'); + JAVASCRIPT + + find("#error-toggle toggle-switch").click + wait_for_toggle_switch_spinner + + refute_selector("#error-toggle [data-target='toggle-switch.errorIcon']") + refute_selector("#error-toggle", text: "Bad CSRF token") + end + + private + + def wait_for_toggle_switch_spinner + refute_selector("[data-target='toggle-switch.loadingSpinner']") + end end end