diff --git a/app/assets/stylesheets/base/breakpoints.scss b/app/assets/stylesheets/base/breakpoints.scss index 3b80daf5cf..180daab2ff 100644 --- a/app/assets/stylesheets/base/breakpoints.scss +++ b/app/assets/stylesheets/base/breakpoints.scss @@ -1,4 +1,6 @@ $small: 640px; $medium: 1024px; -$mobile: $medium; -$large: 1200px; \ No newline at end of file +$large: 1200px; + +// If you adjust this breakpoint, you also need to adjust the breakpoint value in app/javascript/controllers/sidebar-controller.js +$mobile: 768px; diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index db380c5fdb..ab60d692c9 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -1,2 +1,7 @@ $primary: #00447c; $red: #dc3545; + +// ======== Sidebar colors +$sidebar-inactive: #9AA4CA; +$sidebar-active: #365CF5; +$sidebar-dark: #1A2142; diff --git a/app/assets/stylesheets/shared/sidebar.scss b/app/assets/stylesheets/shared/sidebar.scss index 1ee4ca688c..b7a044a55d 100644 --- a/app/assets/stylesheets/shared/sidebar.scss +++ b/app/assets/stylesheets/shared/sidebar.scss @@ -53,6 +53,37 @@ overflow-y: auto; } +.logo-div { + height: 80px; +} + +// Stops weird text wrapping when opening and closing sidebar +.nav-item { + width: 250px; +} + +.nav-item > a { + color: globals.$sidebar-inactive !important; + height: 44px; + + &:hover { + color: globals.$sidebar-active !important; + + span { + color: globals.$sidebar-dark !important; + } + } +} + +.nav-item.active > a { + color: globals.$sidebar-dark !important; +} + +.btn-sidebar { + color: #fff !important; + background-color: globals.$sidebar-active !important; +} + .sidebar-wrapper .sidebar-menu { margin-top: 24px; .list-group-item { @@ -151,3 +182,32 @@ margin-top: 72px; } } + +// These overrides are for larger than mobile screens +@media only screen and (min-width: screen-sizes.$mobile) { + .sidebar-nav-wrapper { + -webkit-transform: translateX(0px) !important; + -moz-transform: translateX(0px) !important; + -ms-transform: translateX(0px) !important; + -o-transform: translateX(0px) !important; + transform: translateX(0px) !important; + + &.active { + width: 66px; + + -webkit-transform: translateX(0px) !important; + -moz-transform: translateX(0px) !important; + -ms-transform: translateX(0px) !important; + -o-transform: translateX(0px) !important; + transform: translateX(0px) !important; + } + } + + .main-wrapper { + margin-left: 250px !important; + + &.active { + margin-left: 66px !important; + } + } +} diff --git a/app/components/sidebar/group_component.html.erb b/app/components/sidebar/group_component.html.erb new file mode 100644 index 0000000000..017193d822 --- /dev/null +++ b/app/components/sidebar/group_component.html.erb @@ -0,0 +1,21 @@ + diff --git a/app/components/sidebar/group_component.rb b/app/components/sidebar/group_component.rb new file mode 100644 index 0000000000..db75ef20d7 --- /dev/null +++ b/app/components/sidebar/group_component.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class Sidebar::GroupComponent < ViewComponent::Base + renders_many :links, Sidebar::LinkComponent + + # @param title [String] the title/label for the link + # @param icon [String] the lni icon, pass just the name of the icon (ie. for lni-star --> icon: "star") + # @param render_check [Boolean] whether or not to display the link + def initialize(title:, icon:, render_check: true) + @title = title + @icon = icon + @render_check = render_check + @identifier = title.downcase.tr(" ", "-") + @class = "#{@identifier} collapsed" + end + + # If there are no links or all links fail their render_check, then don't render this group + # @return [Boolean] + def render? + @render_check && !links.empty? && !links.select(&:render?).empty? + end +end diff --git a/app/components/sidebar/link_component.html.erb b/app/components/sidebar/link_component.html.erb new file mode 100644 index 0000000000..e1907d1c74 --- /dev/null +++ b/app/components/sidebar/link_component.html.erb @@ -0,0 +1,8 @@ +
  • + <%= link_to @path do %> + <% if @icon %> + + <% end %> + <%= @title %> + <% end %> +
  • diff --git a/app/components/sidebar/link_component.rb b/app/components/sidebar/link_component.rb new file mode 100644 index 0000000000..d1f0f745b8 --- /dev/null +++ b/app/components/sidebar/link_component.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Sidebar::LinkComponent < ViewComponent::Base + include SidebarHelper + + # @param title [String] the title/label for the link + # @param path [String] the path to navigate to + # @param icon [String] the lni icon, pass just the name of the icon (ie. for lni-star --> icon: "star") + # @param nav_item [Boolean] whether or not the link should have the nav-item class + # @param render_check [Boolean] whether or not to display the link + def initialize(title:, path:, icon: nil, nav_item: true, render_check: true) + @title = title + @icon = icon + @path = path + @nav_item = nav_item + @render_check = render_check + end + + # Must be moved to this method in order to use the SidebarHelper + def before_render + @class = @nav_item ? "nav-item #{active_class(@path)}" : "" + end + + # @return [Boolean] + def render? + @render_check + end +end diff --git a/app/helpers/sidebar_helper.rb b/app/helpers/sidebar_helper.rb index 98385042a1..e4be743388 100644 --- a/app/helpers/sidebar_helper.rb +++ b/app/helpers/sidebar_helper.rb @@ -15,10 +15,6 @@ def menu_item(label:, path:, visible: false) link_to label, path, class: "list-group-item #{active_class(path)}" if visible end - def get_case_contact_link(casa_case) - case_contacts_path(casa_case_id: casa_case.id) - end - private # private doesn't work in modules. It's here for semantic purposes def active_class(link_path) diff --git a/app/javascript/application.js b/app/javascript/application.js index c661eaf523..234037cf49 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -23,13 +23,11 @@ require('./src/dashboard') require('./src/emancipations') require('./src/import') require('./src/password_confirmation') -require('./src/plainadmin') require('./src/read_more') require('./src/reimbursements') require('./src/reports') require('./src/require_communication_preference') require('./src/select') -require('./src/sidebar') require('./src/tooltip') require('./src/time_zone') require('./src/session_timeout_poller.js') diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 1edcefed63..78f16b4556 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -5,11 +5,19 @@ import { application } from './application' import DismissController from './dismiss_controller' + import ExtendedNestedFormController from './extended_nested_form_controller' + import HelloController from './hello_controller' -import RevealController from 'stimulus-reveal-controller' +import NavbarController from './navbar_controller' + +import SidebarController from './sidebar_controller' + +import SidebarGroupController from './sidebar_group_controller' application.register('dismiss', DismissController) application.register('extended-nested-form', ExtendedNestedFormController) application.register('hello', HelloController) -application.register('reveal', RevealController) +application.register('navbar', NavbarController) +application.register('sidebar', SidebarController) +application.register('sidebar-group', SidebarGroupController) diff --git a/app/javascript/controllers/navbar_controller.js b/app/javascript/controllers/navbar_controller.js new file mode 100644 index 0000000000..7588a97d6b --- /dev/null +++ b/app/javascript/controllers/navbar_controller.js @@ -0,0 +1,10 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static outlets = ['sidebar'] + + click () { + // This simulates a click action on the sidebar-controller + this.sidebarOutlet.click() + } +} diff --git a/app/javascript/controllers/sidebar_controller.js b/app/javascript/controllers/sidebar_controller.js new file mode 100644 index 0000000000..b0bfa3fc41 --- /dev/null +++ b/app/javascript/controllers/sidebar_controller.js @@ -0,0 +1,61 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = ['sidebar', 'menu', 'logo', 'linkTitle', 'groupList'] + static values = { + open: Boolean, + breakpoint: { type: Number, default: 768 } + } + + static outlets = ['sidebar-group'] + + click () { + this.openValue = !this.openValue + this.toggleSidebar() + if (this.isNotMobile()) { + this.toggleLinks() + const mainWrapper = document.querySelector('.main-wrapper') + mainWrapper.classList.toggle('active') + } else { + this.toggleOverlay() + } + } + + hoverOn () { + this.toggleHover() + } + + hoverOff () { + this.toggleHover() + } + + toggleHover () { + if (!this.openValue && this.isNotMobile()) { + this.toggleSidebar() + this.toggleLinks() + } + } + + toggleSidebar () { + this.sidebarTarget.classList.toggle('active') + } + + toggleLinks () { + this.linkTitleTargets.forEach((target) => { + target.classList.toggle('d-none') + }) + this.groupListTargets.forEach((list) => { + list.classList.toggle('nav-item-has-children') + }) + this.logoTarget.classList.toggle('d-none') + } + + toggleOverlay () { + const overlay = document.querySelector('.overlay') + overlay.classList.toggle('active') + } + + isNotMobile () { + return window.innerWidth >= this.breakpointValue + } +} diff --git a/app/javascript/controllers/sidebar_group_controller.js b/app/javascript/controllers/sidebar_group_controller.js new file mode 100644 index 0000000000..dbadb2356d --- /dev/null +++ b/app/javascript/controllers/sidebar_group_controller.js @@ -0,0 +1,19 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = ['title', 'list', 'link'] + + connect () { + this.toggleShow() + } + + // Expands list if a link is active + toggleShow () { + this.linkTargets.forEach((link) => { + if (link.classList.contains('active')) { + this.titleTarget.classList.remove('collapsed') + this.listTarget.classList.add('show') + } + }) + } +} diff --git a/app/javascript/src/plainadmin.js b/app/javascript/src/plainadmin.js deleted file mode 100644 index b90adb662e..0000000000 --- a/app/javascript/src/plainadmin.js +++ /dev/null @@ -1,37 +0,0 @@ -(function () { - /* ========= sidebar toggle ======== */ - const sidebarNavWrapper = document.querySelector('.sidebar-nav-wrapper') - const mainWrapper = document.querySelector('.main-wrapper') - const menuToggleButton = document.querySelector('#menu-toggle') - const menuToggleButtonIcon = document.querySelector('#menu-toggle i') - const overlay = document.querySelector('.overlay') - - if (menuToggleButton) { - menuToggleButton.addEventListener('click', () => { - sidebarNavWrapper.classList.toggle('active') - overlay.classList.add('active') - mainWrapper.classList.toggle('active') - - if (document.body.clientWidth > 1200) { - if (menuToggleButtonIcon.classList.contains('lni-chevron-left')) { - menuToggleButtonIcon.classList.remove('lni-chevron-left') - menuToggleButtonIcon.classList.add('lni-menu') - } else { - menuToggleButtonIcon.classList.remove('lni-menu') - menuToggleButtonIcon.classList.add('lni-chevron-left') - } - } else { - if (menuToggleButtonIcon.classList.contains('lni-chevron-left')) { - menuToggleButtonIcon.classList.remove('lni-chevron-left') - menuToggleButtonIcon.classList.add('lni-menu') - } - } - }) - } - - overlay?.addEventListener('click', () => { - sidebarNavWrapper.classList.remove('active') - overlay.classList.remove('active') - mainWrapper.classList.remove('active') - }) -})() diff --git a/app/javascript/src/sidebar.js b/app/javascript/src/sidebar.js deleted file mode 100644 index 460b122ebe..0000000000 --- a/app/javascript/src/sidebar.js +++ /dev/null @@ -1,21 +0,0 @@ -/* global $ */ - -function toggleSidebar () { - const isOpen = $('#sidebar-js').hasClass('sidebar-open') - if (isOpen) { - $('#sidebar-js').removeClass('sidebar-open') - } else { - $('#sidebar-js').addClass('sidebar-open') - } -} - -$(() => { // JQuery's callback for the DOM loading - $('#toggle-sidebar-js, #sidebar-js').on('click', toggleSidebar) - - // Show group actions dropdown expanded when any of the child is active - if ($('#ddmenu_55 li').children('a').hasClass('active')) { - $('#ddmenu_55').addClass('show') - } else { - $('#ddmenu_55').removeClass('show') - } -}) diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 86391d74b9..0dea4889d0 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -4,15 +4,9 @@
    -
    - -
    +
    diff --git a/app/views/layouts/_other_duties_nav_item.html.erb b/app/views/layouts/_other_duties_nav_item.html.erb deleted file mode 100644 index 8bdd7e43b8..0000000000 --- a/app/views/layouts/_other_duties_nav_item.html.erb +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb index 2824e56923..fcd04bc085 100644 --- a/app/views/layouts/_sidebar.html.erb +++ b/app/views/layouts/_sidebar.html.erb @@ -1,211 +1,69 @@ -
    +
    +
    + - - -
    + + +
    + diff --git a/app/views/shared/_sidebar_dropdown.html.erb b/app/views/shared/_sidebar_dropdown.html.erb deleted file mode 100644 index f97909ff8c..0000000000 --- a/app/views/shared/_sidebar_dropdown.html.erb +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/spec/components/sidebar/group_component_spec.rb b/spec/components/sidebar/group_component_spec.rb new file mode 100644 index 0000000000..2529710349 --- /dev/null +++ b/spec/components/sidebar/group_component_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Sidebar::GroupComponent, type: :component do + before do + @component = described_class.new(title: "Group Actions", icon: "list") + end + + it "renders component when rendered links are added" do + @component.with_link(title: "Generate Court Reports", icon: "paperclip", path: "/case_court_reports") + @component.with_link(title: "Reimbursement Queue", icon: "money-location", path: "/reimbursements") + render_inline(@component) + + expect(page).to have_css "li[class='nav-item nav-item-has-children group-item']" + expect(page).to have_css "a[class='group-actions collapsed']" + expect(page).to have_css "a[data-bs-target='#ddmenu_group-actions']" + expect(page).to have_css "a[aria-controls='ddmenu_group-actions']" + expect(page).to have_css "i[class='lni mr-10 lni-list']" + expect(page).to have_css "span[data-sidebar-target='linkTitle']", text: "Group Actions" + expect(page).to have_css "ul[id='ddmenu_group-actions']" + end + + it "renders links" do + @component.with_link(title: "Generate Court Reports", icon: "paperclip", path: "/case_court_reports") + @component.with_link(title: "Reimbursement Queue", icon: "money-location", path: "/reimbursements") + render_inline(@component) + + expect(page).to have_css "span[data-sidebar-target='linkTitle']", text: "Generate Court Reports" + expect(page).to have_css "span[data-sidebar-target='linkTitle']", text: "Reimbursement Queue" + end + + it "does not render component if no links are added" do + render_inline(@component) + + expect(page).not_to have_css "li[class='nav-item nav-item-has-children group-item']" + end + + it "does not render component if all links are not rendered" do + @component.with_link(title: "Generate Court Reports", icon: "paperclip", path: "/case_court_reports", render_check: false) + render_inline(@component) + end +end diff --git a/spec/components/sidebar/link_component_spec.rb b/spec/components/sidebar/link_component_spec.rb new file mode 100644 index 0000000000..4983dc62e4 --- /dev/null +++ b/spec/components/sidebar/link_component_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Sidebar::LinkComponent, type: :component do + context "component render" do + it "is by default" do + render_inline(described_class.new(title: "Supervisors", path: "/supervisors", icon: "network")) + + expect(page).to have_css "span[data-sidebar-target='linkTitle']", text: "Supervisors" + expect(page).to have_css "a[href='/supervisors']" + expect(page).to have_css "i[class='lni mr-10 lni-network']" + end + + it "doesn't happen if render_check is false" do + render_inline(described_class.new(title: "Supervisors", path: "/supervisors", icon: "network", render_check: false)) + + expect(page).not_to have_css "span[data-sidebar-target='linkTitle']", text: "Supervisors" + end + end + + context "icon render" do + it "doesn't happen if icon not set" do + render_inline(described_class.new(title: "Supervisors", path: "/supervisors")) + + expect(page).not_to have_css "i" + end + end + + context "active class" do + it "is rendered if request path matches link's path" do + with_request_url "/supervisors" do + render_inline(described_class.new(title: "Supervisors", path: "/supervisors", icon: "network")) + + expect(page).to have_css "li[class='nav-item active']" + end + end + + it "is not rendered if request path doesn't match" do + with_request_url "/volunteers" do + render_inline(described_class.new(title: "Supervisors", path: "/supervisors", icon: "network")) + + expect(page).to have_css "li[class='nav-item ']" + expect(page).to have_no_content "li[class='nav-item active']" + end + end + end +end diff --git a/spec/system/casa_cases/index_spec.rb b/spec/system/casa_cases/index_spec.rb index 4b8ccd60d2..032d8273db 100644 --- a/spec/system/casa_cases/index_spec.rb +++ b/spec/system/casa_cases/index_spec.rb @@ -37,7 +37,7 @@ visit root_path click_on "My Cases" - within "#ddmenu_cases" do + within "#ddmenu_my-cases" do click_on case_number end diff --git a/spec/system/case_contacts/index_spec.rb b/spec/system/case_contacts/index_spec.rb index 4b40dc4fbd..af805b57f3 100644 --- a/spec/system/case_contacts/index_spec.rb +++ b/spec/system/case_contacts/index_spec.rb @@ -103,7 +103,7 @@ # showing all cases visit root_path click_on "Case Contacts" - within "#ddmenu_case_contacts" do + within "#ddmenu_case-contacts" do click_on "All" end expect(page).to have_text("Case 1 Notes") @@ -112,7 +112,7 @@ # showing case 1 visit root_path click_on "Case Contacts" - within "#ddmenu_case_contacts" do + within "#ddmenu_case-contacts" do click_on case_number end expect(page).to have_text("Case 1 Notes") @@ -121,7 +121,7 @@ # showing case 2 visit root_path click_on "Case Contacts" - within "#ddmenu_case_contacts" do + within "#ddmenu_case-contacts" do click_on another_case_number end expect(page).to have_text("Case 2 Notes") @@ -138,7 +138,7 @@ # no contacts because we're only showing case 1 and that occurred before the filter dates visit root_path click_on "Case Contacts" - within "#ddmenu_case_contacts" do + within "#ddmenu_case-contacts" do click_on case_number end expect(page).to_not have_text("Case 1 Notes")