diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png new file mode 100644 index 0000000000000000000000000000000000000000..8879d26d341186a1161a7dd1d83f108ad9fe5b12 Binary files /dev/null and b/app/assets/images/new_nav.png differ diff --git a/app/assets/images/old_nav.png b/app/assets/images/old_nav.png new file mode 100644 index 0000000000000000000000000000000000000000..23fae7aa19e9f20fd8d258bcea1196844b3b3f08 Binary files /dev/null and b/app/assets/images/old_nav.png differ diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 88b4b567fa9e76d9ca4b45573fb0364938fea49e..31a86090242b793ca103ac4692dd811c08976a36 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -55,6 +55,7 @@ import RefSelectDropdown from './ref_select_dropdown'; import GfmAutoComplete from './gfm_auto_complete'; import ShortcutsBlob from './shortcuts_blob'; import initSettingsPanels from './settings_panels'; +import initExperimentalFlags from './experimental_flags'; (function() { var Dispatcher; @@ -120,6 +121,9 @@ import initSettingsPanels from './settings_panels'; } switch (page) { + case 'profiles:preferences:show': + initExperimentalFlags(); + break; case 'sessions:new': new UsernameValidator(); new ActiveTabMemoizer(); diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js new file mode 100644 index 0000000000000000000000000000000000000000..dbd3843cef7db90d2d02a55f8295721d4260a1dc --- /dev/null +++ b/app/assets/javascripts/experimental_flags.js @@ -0,0 +1,11 @@ +import Cookies from 'js-cookie'; + +export default () => { + $('.js-experiment-feature-toggle').on('change', (e) => { + const el = e.target; + + Cookies.set(el.name, el.value, { + expires: 365 * 10, + }); + }); +}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index ed7629948ca2fce5e6c34bbecf35b9089b56abea..d27b4ec78c6226015870f85a0cacbf40e99d95bb 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -299,9 +299,10 @@ $(function () { // Commit show suppressed diff }); $('.navbar-toggle').on('click', function () { - $('.header-content .title').toggle(); + $('.header-content .title, .header-content .navbar-sub-nav').toggle(); $('.header-content .header-logo').toggle(); $('.header-content .navbar-collapse').toggle(); + $('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle(); return $('.navbar-toggle').toggleClass('active'); }); // Show/hide comments on diff diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 94986badaf982f96457bb4c3021de24c357a2e0b..5bd6c095109809ec32efda215f4899051a4e4bf2 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -34,6 +34,8 @@ header { top: 0; left: 0; right: 0; + color: $gl-text-color-secondary; + border-radius: 0; @media (max-width: $screen-xs-min) { padding: 0 16px; @@ -59,7 +61,7 @@ header { padding: 0; .nav > li > a { - color: $gl-text-color-secondary; + color: currentColor; font-size: 18px; padding: 0; margin: (($header-height - 28) / 2) 3px; @@ -84,7 +86,7 @@ header { &:hover, &:focus, &:active { - background-color: $gray-light; + background-color: transparent; color: $gl-text-color; svg { @@ -96,13 +98,19 @@ header { font-size: 14px; } + .fa-chevron-down { + position: relative; + top: -3px; + font-size: 10px; + } + svg { position: relative; top: 2px; height: 17px; // hack to get SVG to line up with FA icons width: 23px; - fill: $gl-text-color-secondary; + fill: currentColor; } } @@ -225,7 +233,7 @@ header { } a { - color: $gl-text-color; + color: currentColor; &:hover { text-decoration: underline; @@ -346,6 +354,7 @@ header { width: auto; min-width: 140px; margin-top: -5px; + color: $gl-text-color; left: auto; .current-user { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 476427c1593b39832ec790e5f28ae4896c129495..630f557602cfbf7922d8120928c58c98baccec30 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -74,6 +74,10 @@ $red-700: #a62d19; $red-800: #8b2615; $red-900: #711e11; +$purple-700: #4a2192; +$purple-800: #2c0a5c; +$purple-900: #380d75; + $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss new file mode 100644 index 0000000000000000000000000000000000000000..441bfc479f6e8a7aa08dba987c0a9078e689c53f --- /dev/null +++ b/app/assets/stylesheets/new_nav.scss @@ -0,0 +1,267 @@ +@import "framework/variables"; +@import 'framework/tw_bootstrap_variables'; +@import "bootstrap/variables"; + +header.navbar-gitlab-new { + color: $white-light; + background-color: $purple-900; + border-bottom: 0; + + .header-content { + padding-left: 0; + + .title-container { + padding-top: 0; + overflow: visible; + } + + .title { + display: block; + height: 100%; + padding-right: 0; + color: currentColor; + + > a { + display: flex; + align-items: center; + height: 100%; + padding-top: 3px; + padding-right: $gl-padding; + padding-left: $gl-padding; + margin-left: -$gl-padding; + border-bottom: 3px solid transparent; + + @media (min-width: $screen-sm-min) { + padding-right: $gl-padding; + padding-left: $gl-padding; + } + + svg { + margin-top: -3px; + + @media (min-width: $screen-sm-min) { + margin-right: 10px; + } + } + + &:hover, + &:focus { + color: currentColor; + text-decoration: none; + border-bottom-color: $white-light; + } + } + } + + .dropdown.open { + > a { + border-bottom-color: $white-light; + } + } + + .dropdown-menu { + margin-top: 4px; + min-width: 130px; + + @media (max-width: $screen-xs-max) { + left: auto; + right: 0; + } + } + } + + .navbar-collapse { + padding-left: 0; + color: $white-light; + box-shadow: 0; + + @media (max-width: $screen-xs-max) { + margin-left: -$gl-padding; + margin-right: -10px; + } + + .dropdown-bold-header { + color: initial; + } + + .nav { + > li:not(.hidden-xs) a { + @media (max-width: $screen-xs-max) { + margin-left: 0; + min-width: 100%; + } + } + } + } + + .container-fluid { + .navbar-toggle { + min-width: 45px; + padding: 6px $gl-padding; + margin-right: -7px; + font-size: 14px; + text-align: center; + color: currentColor; + border-left: 1px solid lighten($purple-700, 10%); + + &:hover, + &:focus, + &.active { + color: currentColor; + background-color: transparent; + } + } + + .navbar-nav { + @media (max-width: $screen-xs-max) { + display: flex; + padding-right: 10px; + } + + li { + .badge { + box-shadow: none; + } + } + } + + .nav > li { + &.header-user { + @media (max-width: $screen-xs-max) { + padding-left: 10px; + } + } + + > a { + background: none; + opacity: .9; + will-change: opacity; + + &.header-user-dropdown-toggle { + .header-user-avatar { + border-color: $white-light; + } + } + + &:hover, + &:focus { + color: $white-light; + opacity: 1; + + > svg { + fill: $white-light; + } + + &.header-user-dropdown-toggle { + .header-user-avatar { + border-color: $white-light; + } + } + } + } + } + } +} + +.navbar-sub-nav { + display: flex; + margin-bottom: 0; + color: $white-light; + + > li { + &.active > a, + a:hover, + a:focus { + border-bottom-color: $white-light; + text-decoration: none; + outline: 0; + opacity: 1; + } + + > a { + display: block; + padding: 16px 10px 13px; + font-size: 13px; + color: currentColor; + border-bottom: 3px solid transparent; + opacity: .9; + will-change: opacity; + + @media (min-width: $screen-sm-min) { + padding: 15px $gl-padding 12px; + font-size: 14px; + } + } + } + + .dropdown-chevron { + position: relative; + top: -1px; + font-size: 10px; + } +} + +.header-user .dropdown-menu-nav, +.header-new .dropdown-menu-nav { + margin-top: 4px; +} + +.search { + form { + border-color: $purple-800; + + &:hover { + border-color: rgba($white-light, .6); + box-shadow: none; + } + } + + &.search-active form { + border-color: $white-light; + } + + form, + .search-input { + background-color: $purple-700; + } + + .search-input { + color: $white-light; + } + + .search-input::placeholder { + color: rgba($white-light, .6); + } + + .location-badge { + font-size: 12px; + color: rgba($white-light, .6); + background-color: $purple-800; + transition: color 0.15s; + will-change: color; + } + + .search-input-wrap { + .search-icon, + .clear-icon { + color: rgba($white-light, .6); + } + } + + &.search-active { + .location-badge { + color: $white-light; + background-color: $purple-800; + } + + .search-input-wrap { + .search-icon { + color: rgba($white-light, .6); + } + + .clear-icon { + color: $white-light; + } + } + } +} diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a3b243fccb73e8d9ae61da02fca8d9b345cb3381..dc7ff78f3df484401ca65a10d32fd78af45f05fb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -300,4 +300,12 @@ module ApplicationHelper "https://www.twitter.com/#{name}" end end + + def can_toggle_new_nav? + Rails.env.development? + end + + def show_new_nav? + cookies["new_nav"] == "true" + end end diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index eea33b5966f729bb3a81924f76596f24f1227511..f7a1d7e88449cabdc853a97c86b982586328918d 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -30,6 +30,9 @@ = stylesheet_link_tag "test", media: "all" if Rails.env.test? = stylesheet_link_tag 'peek' if peek_enabled? + - if show_new_nav? + = stylesheet_link_tag "new_nav", media: "all" + = Gon::Base.render_data = webpack_bundle_tag "runtime" diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2b07273a0a84debea7f08db8b7df92da2355ce0e..d879df8fc820cbf617ebfd0bc40e4b86758a9a77 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -4,7 +4,10 @@ %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } = render "layouts/init_auto_complete" if @gfm_form = render 'peek/bar' - = render "layouts/header/default", title: header_title + - if show_new_nav? + = render "layouts/header/new" + - else + = render "layouts/header/default", title: header_title = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 249253f49069acf7968c65878567746d851020ed..f056c0af968bec0d267e43969e844a10726c6855 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -74,6 +74,9 @@ = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } %li = link_to "Settings", profile_path + - if can_toggle_new_nav? + %li + = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation") %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..c0833c6491121dc4c71ba223038ae4ca12e66934 --- /dev/null +++ b/app/views/layouts/header/_new.html.haml @@ -0,0 +1,93 @@ +%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class } + %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content + .container-fluid + .header-content + .title-container + %h1.title + = link_to root_path, title: 'Dashboard' do + = brand_header_logo + %span.hidden-xs + GitLab + + - if current_user + = render "layouts/nav/new_dashboard" + - else + = render "layouts/nav/new_explore" + + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li.hidden-sm.hidden-xs + = render 'layouts/search' unless current_controller?(:search) + %li.visible-sm-inline-block.visible-xs-inline-block + = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('search') + - if current_user + - if session[:impersonator_id] + %li.impersonation + = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = icon('user-secret fw') + - if current_user.admin? + %li + = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('wrench fw') + = render 'layouts/header/new_dropdown' + - if Gitlab::Sherlock.enabled? + %li + = link_to sherlock_transactions_path, title: 'Sherlock Transactions', + data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('tachometer fw') + %li + = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('hashtag fw') + - issues_count = assigned_issuables_count(:issues) + %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } + = number_with_delimiter(issues_count) + %li + = link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = custom_icon('mr_bold') + - merge_requests_count = assigned_issuables_count(:merge_requests) + %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) } + = number_with_delimiter(merge_requests_count) + %li + = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('check-circle fw') + %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) } + = todos_count_format(todos_pending_count) + %li.header-user.dropdown + = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do + = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar" + = icon('chevron-down') + .dropdown-menu-nav.dropdown-menu-align-right + %ul + %li.current-user + .user-name.bold + = current_user.name + @#{current_user.username} + %li.divider + %li + = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } + %li + = link_to "Settings", profile_path + %li + = link_to "Turn off new nav", profile_preferences_path(anchor: "new-navigation") + %li.divider + %li + = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" + - else + %li + %div + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' + + %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' } + %span.sr-only Toggle navigation + = icon('ellipsis-v', class: 'js-navbar-toggle-right') + = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;') + + = yield :header_content + += render 'shared/outdated_browser' + +- if @project && !@project.empty_repo? + - if ref = @ref || @project.repository.root_ref + :javascript + var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}"; diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 969c423b032bd679a40ab2cc7f2a2211ea80f8d7..9ff1164f2ee33a34d73864e10e8e7403b15663e9 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -1,7 +1,11 @@ %li.header-new.dropdown = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do - = icon('plus fw') - = icon('caret-down') + - if show_new_nav? + = icon('plus') + = icon('chevron-down') + - else + = icon('plus fw') + = icon('caret-down') .dropdown-menu-nav.dropdown-menu-align-right %ul - if @group&.persisted? diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..7109baa4dad17b102c81450acd1779f15360044a --- /dev/null +++ b/app/views/layouts/nav/_new_dashboard.html.haml @@ -0,0 +1,33 @@ +%ul.list-unstyled.navbar-sub-nav + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "home"}) do + = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do + Projects + + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do + = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do + Groups + + = nav_link(path: 'dashboard#activity', html_options: { class: "hidden-xs hidden-sm" }) do + = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do + Activity + + %li.dropdown + %a{ href: "#", data: { toggle: "dropdown" } } + More + = icon("chevron-down", class: "dropdown-chevron") + .dropdown-menu + %ul + = nav_link(path: 'dashboard#activity', html_options: { class: "visible-xs visible-sm" }) do + = link_to activity_dashboard_path, title: 'Activity' do + Activity + + = nav_link(controller: 'dashboard/milestones') do + = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do + Milestones + + = nav_link(controller: 'dashboard/snippets') do + = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do + Snippets + %li.divider + %li + = link_to "Help", help_path, title: 'About GitLab CE' diff --git a/app/views/layouts/nav/_new_explore.html.haml b/app/views/layouts/nav/_new_explore.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..40385f251e39b38e292c7c5078f90e07ab9286a7 --- /dev/null +++ b/app/views/layouts/nav/_new_explore.html.haml @@ -0,0 +1,19 @@ +%ul.list-unstyled.navbar-sub-nav + = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do + = link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do + Projects + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do + = link_to explore_groups_path, title: 'Groups', class: 'dashboard-shortcuts-groups' do + Groups + %li.dropdown + %a{ href: "#", data: { toggle: "dropdown" } } + More + = icon("chevron-down", class: "dropdown-chevron") + .dropdown-menu + %ul + = nav_link(controller: :snippets) do + = link_to explore_snippets_path, title: 'Snippets', class: 'dashboard-shortcuts-snippets' do + Snippets + %li.divider + %li + = link_to "Help", help_path, title: 'About GitLab CE' diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 0ff19b3eab13399ccb971267eb5cbba17576f2ad..0b5995415e963be17462cbcd7a6d5876721d4872 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -15,6 +15,25 @@ .preview= image_tag "#{scheme.css_class}-scheme-preview.png" = f.radio_button :color_scheme_id, scheme.id = scheme.name + - if can_toggle_new_nav? + .col-sm-12 + %hr + .col-lg-3.profile-settings-sidebar#new-navigation + %h4.prepend-top-0 + New Navigation + %p + This setting allows you to turn on or off the new upcoming navigation concept. + = succeed '.' do + = link_to 'Learn more', '', target: '_blank' + .col-lg-9.syntax-theme + = label_tag do + .preview= image_tag "old_nav.png" + %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } + Old + = label_tag do + .preview= image_tag "new_nav.png" + %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? } + New .col-sm-12 %hr .col-lg-3.profile-settings-sidebar diff --git a/config/application.rb b/config/application.rb index 8bbecf3ed0f5287cd2b45e62406b5ea7f27a6526..12242c3b0f5229af3a26d13c465180e3b4730e19 100644 --- a/config/application.rb +++ b/config/application.rb @@ -109,6 +109,7 @@ module Gitlab config.assets.precompile << "lib/ace.js" config.assets.precompile << "vendor/assets/fonts/*" config.assets.precompile << "test.css" + config.assets.precompile << "new_nav.css" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0'