diff --git a/Makefile b/Makefile index e521072f34d..c7eeaae248e 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,13 @@ lint_yarn_workspaces: ## Lints Yarn workspace packages scripts/validate-workspaces.js lint_asset_bundle_size: ## Lints JavaScript and CSS compiled bundle size - find app/assets/builds/application.css -size -270000c | grep . + @# This enforces an asset size budget to ensure that download sizes are reasonable and to protect + @# against accidentally importing large pieces of third-party libraries. If you're here debugging + @# a failing build, check to ensure that you've not added more JavaScript or CSS than necessary, + @# and you have no options to split that from the common bundles. If you need to increase this + @# budget and accept the fact that this will force end-users to endure longer load times, you + @# should set the new budget to within a few thousand bytes of the production-compiled size. + find app/assets/builds/application.css -size -235000c | grep . find public/packs/js/application-*.digested.js -size -5000c | grep . lint_migrations: diff --git a/app/assets/stylesheets/_uswds.scss b/app/assets/stylesheets/_uswds.scss index 0595c4695e1..18a3348ec68 100644 --- a/app/assets/stylesheets/_uswds.scss +++ b/app/assets/stylesheets/_uswds.scss @@ -9,7 +9,6 @@ @forward 'usa-collection'; @forward 'usa-form'; @forward 'usa-header'; -@forward 'usa-icon-list'; @forward 'usa-icon'; @forward 'usa-layout-grid'; @forward 'usa-link'; diff --git a/app/components/icon_list_component.html.erb b/app/components/icon_list_component.html.erb new file mode 100644 index 00000000000..48c260bf7d5 --- /dev/null +++ b/app/components/icon_list_component.html.erb @@ -0,0 +1,5 @@ +<%= content_tag(:ul, **tag_options, class: css_class) do %> + <% items.each do |item| %> + <%= item %> + <% end %> +<% end %> diff --git a/app/components/icon_list_component.rb b/app/components/icon_list_component.rb new file mode 100644 index 00000000000..58a37885fd0 --- /dev/null +++ b/app/components/icon_list_component.rb @@ -0,0 +1,35 @@ +class IconListComponent < BaseComponent + renders_many :items, ->(**kwargs, &block) do + IconListItemComponent.new(icon:, color:, **kwargs, &block) + end + + attr_reader :icon, :size, :color, :tag_options + + def initialize(icon: nil, size: 'md', color: nil, **tag_options) + @icon = icon + @size = size + @color = color + @tag_options = tag_options + end + + def css_class + classes = ['usa-icon-list', *tag_options[:class]] + classes << ["usa-icon-list--size-#{size}"] if size + classes + end + + class IconListItemComponent < BaseComponent + attr_reader :icon, :color + + def initialize(icon:, color:) + @icon = icon + @color = color + end + + def icon_css_class + classes = ['usa-icon-list__icon'] + classes << "text-#{color}" if color + classes + end + end +end diff --git a/app/components/icon_list_component.scss b/app/components/icon_list_component.scss new file mode 100644 index 00000000000..018f0f4b5f2 --- /dev/null +++ b/app/components/icon_list_component.scss @@ -0,0 +1,3 @@ +@use 'uswds-core' as *; + +@forward 'usa-icon-list'; diff --git a/app/components/icon_list_item_component.html.erb b/app/components/icon_list_item_component.html.erb new file mode 100644 index 00000000000..05bab773005 --- /dev/null +++ b/app/components/icon_list_item_component.html.erb @@ -0,0 +1,6 @@ +
  • + <%= content_tag(:div, class: icon_css_class) do %> + <%= render IconComponent.new(icon: icon) %> + <% end %> +
    <%= content %>
    +
  • diff --git a/app/components/process_list_component.rb b/app/components/process_list_component.rb index 6ba17d09c7d..1ee0c5b2b7a 100644 --- a/app/components/process_list_component.rb +++ b/app/components/process_list_component.rb @@ -1,7 +1,7 @@ class ProcessListComponent < BaseComponent - renders_many :items, ->(**kwargs, &block) { + renders_many :items, ->(**kwargs, &block) do ProcessListItemComponent.new(heading_level:, **kwargs, &block) - } + end attr_reader :heading_level, :big, :connected, :tag_options diff --git a/app/views/partials/multi_factor_authentication/_selected_mfa_option.html.erb b/app/views/partials/multi_factor_authentication/_selected_mfa_option.html.erb deleted file mode 100644 index e3fd5365b63..00000000000 --- a/app/views/partials/multi_factor_authentication/_selected_mfa_option.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -
  • -
    - <%= render IconComponent.new(icon: :check_circle, width: 24, height: 24) %> -
    -
    - <%= option.label %> <%= option.mfa_added_label %> -
    -
  • diff --git a/app/views/users/two_factor_authentication_setup/index.html.erb b/app/views/users/two_factor_authentication_setup/index.html.erb index b9cb887f760..6c76f7de089 100644 --- a/app/views/users/two_factor_authentication_setup/index.html.erb +++ b/app/views/users/two_factor_authentication_setup/index.html.erb @@ -19,13 +19,15 @@ <%= t('headings.account.two_factor') %> - + <% end %> <% end %> <%= simple_form_for @two_factor_options_form, diff --git a/spec/components/icon_list_component_spec.rb b/spec/components/icon_list_component_spec.rb new file mode 100644 index 00000000000..b414b631c23 --- /dev/null +++ b/spec/components/icon_list_component_spec.rb @@ -0,0 +1,69 @@ +require 'rails_helper' + +RSpec.describe IconListComponent, type: :component do + subject(:rendered) { render_inline IconListComponent.new } + + it 'renders with expected attributes for default state' do + expect(rendered).to have_css('.usa-icon-list.usa-icon-list--size-md') + end + + context 'with explicitly nil size' do + subject(:rendered) { render_inline IconListComponent.new(size: nil) } + + it 'renders without size modifier css class' do + expect(rendered).not_to have_css('[class*=usa-icon-list--size-]') + end + end + + context 'with additional tag options' do + it 'applies tag options to wrapper element' do + rendered = render_inline IconListComponent.new(class: 'custom-class', data: { foo: 'bar' }) + + expect(rendered).to have_css('.usa-icon-list.custom-class[data-foo="bar"]') + end + end + + context 'with slotted items' do + subject(:rendered) do + render_inline IconListComponent.new(icon: :cancel) do |c| + c.with_item { 'First' } + c.with_item { 'Second' } + end + end + + it 'renders items with default color' do + expect(rendered).to have_css('.usa-icon-list__icon:not([class*="text-"])', count: 2) + expect(rendered).to have_css('.usa-icon use[href$=".svg#cancel"]', count: 2) + end + + context 'with icon or color attributes specified on parent component' do + subject(:rendered) do + render_inline IconListComponent.new(icon: :cancel, color: :error) do |c| + c.with_item { 'First' } + c.with_item { 'Second' } + end + end + + it 'passes those attributes to slotted items' do + expect(rendered).to have_css('.usa-icon-list__icon.text-error', count: 2) + expect(rendered).to have_css('.usa-icon use[href$=".svg#cancel"]', count: 2) + end + end + + context 'with icon and color attributes specified on items' do + subject(:rendered) do + render_inline IconListComponent.new do |c| + c.with_item(icon: :check_circle, color: :success) { 'First' } + c.with_item(icon: :cancel, color: :error) { 'Second' } + end + end + + it 'renders items with their attributes' do + expect(rendered).to have_css('.usa-icon-list__icon.text-success', count: 1) + expect(rendered).to have_css('.usa-icon use[href$=".svg#check_circle"]', count: 1) + expect(rendered).to have_css('.usa-icon-list__icon.text-error', count: 1) + expect(rendered).to have_css('.usa-icon use[href$=".svg#cancel"]', count: 1) + end + end + end +end diff --git a/spec/components/previews/icon_list_component_preview.rb b/spec/components/previews/icon_list_component_preview.rb new file mode 100644 index 00000000000..f2fe0301c90 --- /dev/null +++ b/spec/components/previews/icon_list_component_preview.rb @@ -0,0 +1,25 @@ +class IconListComponentPreview < BaseComponentPreview + # @!group Preview + def default + render(IconListComponent.new(icon: :cancel, color: :error)) do |c| + c.with_item { 'You cannot pass identity verification if your ID is expired.' } + c.with_item { 'You cannot use extension documents in place of an unexpired ID.' } + c.with_item { 'You cannot use a paper or temporary ID.' } + end + end + # @!endgroup + + # rubocop:disable Layout/LineLength + + # @param icon select [~,accessibility_new,accessible_forward,account_balance,account_box,account_circle,add,add_circle,add_circle_outline,alarm,alternate_email,announcement,api,arrow_back,arrow_downward,arrow_drop_down,arrow_drop_up,arrow_forward,arrow_upward,assessment,attach_file,attach_money,autorenew,backpack,bathtub,bedding,bookmark,bug_report,build,calendar_today,campaign,camping,cancel,chat,check,check_box_outline_blank,check_circle,check_circle_outline,checkroom,chevron_left,chevron_right,clean_hands,close,closed_caption,clothes,cloud,code,comment,connect_without_contact,construction,construction_worker,contact_page,content_copy,coronavirus,credit_card,deck,delete,device_thermostat,directions,directions_bike,directions_bus,directions_car,directions_walk,do_not_disturb,do_not_touch,drag_handle,eco,edit,electrical_services,emoji_events,error,error_outline,event,expand_less,expand_more,facebook,fast_forward,fast_rewind,favorite,favorite_border,file_download,file_present,file_upload,filter_alt,filter_list,fingerprint,first_page,flag,flickr,flight,flooding,folder,folder_open,format_quote,format_size,forum,github,grid_view,group_add,groups,hearing,help,help_outline,highlight_off,history,home,hospital,hotel,hourglass_empty,hurricane,identification,image,info,info_outline,insights,instagram,keyboard,label,language,last_page,launch,lightbulb,lightbulb_outline,link,link_off,list,local_cafe,local_fire_department,local_gas_station,local_grocery_store,local_hospital,local_laundry_service,local_library,local_offer,local_parking,local_pharmacy,local_police,local_taxi,location_city,location_on,lock,lock_open,lock_outline,login,logout,loop,mail,mail_outline,map,masks,medical_services,menu,military_tech,more_horiz,more_vert,my_location,navigate_before,navigate_far_before,navigate_far_next,navigate_next,near_me,notifications,notifications_active,notifications_none,notifications_off,park,people,person,pets,phone,photo_camera,print,priority_high,public,push_pin,radio_button_unchecked,rain,reduce_capacity,remove,report,restaurant,rss_feed,safety_divider,sanitizer,save_alt,schedule,school,science,search,security,send,sentiment_dissatisfied,sentiment_neutral,sentiment_satisfied,sentiment_satisfied_alt,sentiment_very_dissatisfied,settings,severe_weather,share,shield,shopping_basket,snow,soap,social_distance,sort_arrow,spellcheck,star,star_half,star_outline,store,support,support_agent,text_fields,thumb_down_alt,thumb_up_alt,timer,toggle_off,toggle_on,topic,tornado,translate,trending_down,trending_up,twitter,undo,unfold_less,unfold_more,update,upload_file,verified,verified_user,visibility,visibility_off,volume_off,warning,wash,wifi,work,youtube,zoom_in,zoom_out,zoom_out_map] + # @param size select [~,md] + # @param color select [~,info,error,warning,success] + def workbench(icon: :cancel, size: 'md', color: 'error') + render(IconListComponent.new(icon:, size:, **{ color: }.compact)) do |c| + c.with_item { 'You cannot pass identity verification if your ID is expired.' } + c.with_item { 'You cannot use extension documents in place of an unexpired ID.' } + c.with_item { 'You cannot use a paper or temporary ID.' } + end + end + # rubocop:enable Layout/LineLength +end