diff --git a/website/src/styles/bootstrap/style-override.scss b/website/src/styles/bootstrap/style-override.scss index 95f20c64c6..59afcd10b4 100644 --- a/website/src/styles/bootstrap/style-override.scss +++ b/website/src/styles/bootstrap/style-override.scss @@ -12,8 +12,7 @@ mark { // Dropdown menu .dropdown-menu { - width: 100%; - padding: 0.1rem 0; + color: var(--body-color); box-shadow: 0 2px 6px rgba(#000, 0.25); @include scrollable-y; @@ -24,17 +23,51 @@ mark { } .dropdown-item { + $icon-size: $font-size-base; + + display: flex; + align-items: flex-start; + border-radius: 0.25rem; color: var(--body-color); cursor: pointer; + .dropdown-item-left-icon { + flex: 0 0 auto; + width: $icon-size; + height: $icon-size; + margin-top: 0.14rem; + margin-right: 0.4rem; + } + + .dropdown-item-right-content { + align-self: center; + margin-left: auto; + + &.dropdown-item-right-content-icon { + width: $icon-size * 0.75; + height: $icon-size * 0.75; + color: var(--gray-mid); + } + } + + &:hover, + &:focus, + &:active { + .dropdown-item-right-content { + &.dropdown-item-right-content-icon { + color: unset; + } + } + } + &:hover, &:focus, &.dropdown-selected { - color: theme-color(); + color: var(--body-color); background: var(--gray-lightest); } - &.dropdown-selected:active { + &:active { color: #fff; background: theme-color(); } @@ -46,6 +79,11 @@ mark { &.dropdown-selected { background: rgba(theme-color('danger'), 0.2); } + + &:active { + color: #fff; + background: theme-color(); + } } @include touchscreen-only { @@ -53,11 +91,6 @@ mark { padding-top: $touch-horizontal-padding; padding-bottom: $touch-horizontal-padding; - border-bottom: 1px solid var(--gray-lighter); - - &:last-child { - border-bottom: 0; - } } } diff --git a/website/src/styles/bootstrap/variable-overrides.scss b/website/src/styles/bootstrap/variable-overrides.scss index 136283a6c8..0351276e14 100644 --- a/website/src/styles/bootstrap/variable-overrides.scss +++ b/website/src/styles/bootstrap/variable-overrides.scss @@ -1,12 +1,12 @@ // Override the default bootstrap variables in this file. // Colors -$gray-dark: #222324; -$gray: #69707a; -$gray-mid: #848490; -$gray-light: #aeb1b5; -$gray-lighter: #d3d6db; -$gray-lightest: #f3f5f8; +$gray-dark: #222324; +$gray: #69707a; +$gray-mid: #848490; +$gray-light: #aeb1b5; +$gray-lighter: #d3d6db; +$gray-lightest: #f3f5f8; $primary: #ff5138; $danger: #b71c1c; @@ -20,26 +20,26 @@ $dark: $gray-dark; $body-color: $gray; // Typography -$font-size-xlg: 2rem !default; -$font-size-s: 0.85rem !default; -$font-size-xs: 0.75rem !default; -$font-size-xxs: 0.65rem !default; +$font-size-xlg: 2rem !default; +$font-size-s: 0.85rem !default; +$font-size-xs: 0.75rem !default; +$font-size-xxs: 0.65rem !default; // Fonts -$headings-margin-bottom: 1rem; +$headings-margin-bottom: 1rem; // Form states and alerts -$state-success-text: #fff; -$state-success-bg: theme-color('success'); +$state-success-text: #fff; +$state-success-bg: theme-color('success'); -$state-info-text: #fff; -$state-info-bg: theme-color('info'); +$state-info-text: #fff; +$state-info-bg: theme-color('info'); -$state-warning-text: #fff; -$state-warning-bg: theme-color('warning'); +$state-warning-text: #fff; +$state-warning-bg: theme-color('warning'); -$state-danger-text: #fff; -$state-danger-bg: theme-color('danger'); +$state-danger-text: #fff; +$state-danger-bg: theme-color('danger'); // Element styles $mark-bg: transparent; @@ -56,8 +56,15 @@ $input-focus-color: var(--body-color); $input-focus-border-color: $primary; $input-disabled-bg: var(--body-bg); -$dropdown-border-color: var(--gray-lighter); +$dropdown-padding-x: 0.4rem; +$dropdown-padding-y: $dropdown-padding-x; $dropdown-bg: var(--body-bg); +$dropdown-border-color: var(--gray-lighter); +$dropdown-divider-bg: var(--gray-lighter); +$dropdown-item-padding-y: 0.375rem; +$dropdown-item-padding-x: 0.75rem; +$dropdown-header-color: var(--gray-mid); +$dropdown-header-padding: $dropdown-item-padding-y $dropdown-item-padding-x; $mark-padding: 0; diff --git a/website/src/styles/constants.scss b/website/src/styles/constants.scss index e4e09e82a3..50d89df964 100644 --- a/website/src/styles/constants.scss +++ b/website/src/styles/constants.scss @@ -4,12 +4,7 @@ $navbar-height: 3rem; $navtab-height: 3rem; -$side-nav-width-md: 4rem; -$side-nav-width-lg: 10rem; - -$navtab-shadow-height: 0.8rem; -$navtab-shadow-color: rgba(#000, 0.1); -$navtab-shadow-color-night: rgba(#000, 0.7); +$navbar-shadow-height: 0.2rem; // Type $sm-font-size-ratio: 0.875; @@ -21,6 +16,8 @@ $entering-duration: 0.225s; $desktop-fullscreen-duration: 0.275s; $desktop-exiting-duration: 0.175s; $desktop-entering-duration: 0.195s; + +// Source: https://github.com/material-components/material-components-web/blob/master/packages/mdc-animation/_animation.scss $material-standard-curve: cubic-bezier(0.4, 0, 0.2, 1); $material-deceleration-curve: cubic-bezier(0, 0, 0.2, 1); $material-acceleration-curve: cubic-bezier(0.4, 0, 1, 1); @@ -53,7 +50,7 @@ $nusmods-theme-colors: ( #95aec7, #ae95c7, #c795ae, - #c79595 + #c79595, ), chalk: ( #fb9fb1, @@ -63,7 +60,7 @@ $nusmods-theme-colors: ( #12cfc0, #6fc2ef, #e1a3ee, - #deaf8f + #deaf8f, ), eighties: ( #f2777a, @@ -73,7 +70,7 @@ $nusmods-theme-colors: ( #6cc, #69c, #c9c, - #d27b53 + #d27b53, ), google: ( #cc342b, @@ -83,7 +80,7 @@ $nusmods-theme-colors: ( #3971ed, #79a4f9, #a36ac7, - #ec9998 + #ec9998, ), mocha: ( #cb6077, @@ -93,7 +90,7 @@ $nusmods-theme-colors: ( #7bbda4, #8ab3b5, #a89bb9, - #bb9584 + #bb9584, ), monokai: ( #f92672, @@ -103,7 +100,7 @@ $nusmods-theme-colors: ( #a1efe4, #66d9ef, #ae81ff, - #c63 + #c63, ), ocean: ( #bf616a, @@ -113,7 +110,7 @@ $nusmods-theme-colors: ( #96b5b4, #8fa1b3, #b48ead, - #ab7967 + #ab7967, ), oceanic-next: ( #ec5f67, @@ -123,7 +120,7 @@ $nusmods-theme-colors: ( #5fb3b3, #69c, #c594c5, - #ab7967 + #ab7967, ), paraiso: ( #ef6155, @@ -133,7 +130,7 @@ $nusmods-theme-colors: ( #5bc4bf, #06b6ef, #815ba4, - #e96ba8 + #e96ba8, ), railscasts: ( #da4939, @@ -143,7 +140,7 @@ $nusmods-theme-colors: ( #519f50, #6d9cbe, #b6b3eb, - #bc9458 + #bc9458, ), tomorrow: ( #c66, @@ -153,7 +150,7 @@ $nusmods-theme-colors: ( #8abeb7, #81a2be, #b294bb, - #a3685a + #a3685a, ), twilight: ( #cf6a4c, @@ -163,7 +160,7 @@ $nusmods-theme-colors: ( #afc4db, #7587a6, #9b859d, - #9b703f + #9b703f, ), ); @@ -173,9 +170,7 @@ $snackbar-z-index: 2500; $sentry-z-index: 2000; $modal-z-index: 1500; $module-select-z-index: 960; -$module-finder-search-z-index-sm: 950; $venue-detail-expanded-z-index: 920; -$navtabs-z-index: 900; $navbar-z-index: 890; $module-finder-search-z-index-md: 850; $zindex-dropdown: 820; // Bootstrap override diff --git a/website/src/styles/layout/site.scss b/website/src/styles/layout/site.scss index 368c1f62d9..43facc172a 100644 --- a/website/src/styles/layout/site.scss +++ b/website/src/styles/layout/site.scss @@ -7,14 +7,11 @@ body, // Ensures the page always fill the height of the page. Combined with the // flexbox on .app-container, this allows the footer to be aligned to the // page bottom when there's insufficient page content - height: 100%; + min-height: 100vh; } body { - padding-top: 4rem; - @include media-breakpoint-down(sm) { - padding-top: $navbar-height; font-size: 1rem * $sm-font-size-ratio; } } @@ -36,30 +33,8 @@ a { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); - @include media-breakpoint-down(sm) { - .main-content { - padding-top: 1rem; - } - } - - @include media-breakpoint-up(md) { - > nav { - width: calc(#{$side-nav-width-md} + #{($grid-gutter-width / 2)}); - } - - .main-content { - padding-left: $side-nav-width-md; - } - } - - @include media-breakpoint-up(xl) { - > nav { - width: $side-nav-width-lg; - } - - .main-content { - padding-left: $side-nav-width-lg; - } + .main-content { + padding-top: 1rem; } } diff --git a/website/src/styles/utils/css-variables.scss b/website/src/styles/utils/css-variables.scss index b1c657297a..ae15885897 100644 --- a/website/src/styles/utils/css-variables.scss +++ b/website/src/styles/utils/css-variables.scss @@ -20,7 +20,7 @@ --body-bg-transparent: #{rgba($body-bg, 0)}; // Shadows - --navtab-shadow: #{rgba(#000, 0.1)}; + --navbar-shadow: #{rgba(#000, 0.09)}; // Body font color --body-color: #{$body-color}; @@ -52,7 +52,7 @@ body.mode-dark { --body-bg-transparent: #{rgba($gray-dark, 0)}; // Shadows - --navtab-shadow: #{rgba(#000, 0.26)}; + --navbar-shadow: #{rgba(#000, 0.5)}; // Body font color --body-color: #aaa; diff --git a/website/src/views/AppShell.scss b/website/src/views/AppShell.scss deleted file mode 100644 index c606c31d29..0000000000 --- a/website/src/views/AppShell.scss +++ /dev/null @@ -1,67 +0,0 @@ -@import '~styles/utils/modules-entry.scss'; - -$vert-padding: 0.5rem; -// Keep padding at whole number, 0.875rem = 14px -// See: https://github.com/nusmodifications/nusmods/pull/1567 -$logo-vert-padding: 0.875rem; - -.navbar { - position: fixed; - top: 0; - right: 0; - left: 0; - z-index: $navbar-z-index; - display: flex; - justify-content: space-between; - height: $navbar-height; - padding: 0; // For Microsoft Edge 18 - padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left); - background: var(--gray-lightest); -} - -.brand { - display: flex; - // Explicit width so the logo will not expand - // to occupy the entire navbar - width: 12rem; - padding: $logo-vert-padding $grid-gutter-width; -} - -.brandLogo { - width: 100%; -} - -.navRight { - display: flex; - align-items: center; - padding: $vert-padding $grid-gutter-width/2; -} - -.weekText { - padding-left: $grid-gutter-width/2; - font-size: $font-size-xs; - line-height: 1.15; - text-align: right; -} - -@include media-breakpoint-up(xl) { - .brand { - // Padding to match navtab left padding - padding-left: 2rem; - } - - .navRight { - padding: $vert-padding $grid-gutter-width/2 $vert-padding 2rem; - } -} - -@include media-breakpoint-down(xs) { - .navRight { - display: none; - } - - .brand { - flex: 1; - padding: $logo-vert-padding 0; - } -} diff --git a/website/src/views/AppShell.tsx b/website/src/views/AppShell.tsx index 2fdecff908..868494efeb 100644 --- a/website/src/views/AppShell.tsx +++ b/website/src/views/AppShell.tsx @@ -4,12 +4,11 @@ import type { Semester } from 'types/modules'; import { DARK_MODE } from 'types/settings'; import { Helmet } from 'react-helmet'; -import { NavLink, useHistory } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { useDispatch, useSelector, useStore } from 'react-redux'; import classnames from 'classnames'; import { each } from 'lodash'; -import weekText from 'utils/weekText'; import { captureException } from 'utils/error'; import { openNotification } from 'actions/app'; import { fetchModuleList as fetchModuleListAction } from 'actions/moduleBank'; @@ -18,15 +17,13 @@ import { validateTimetable, } from 'actions/timetables'; import Footer from 'views/layout/Footer'; -import Navtabs from 'views/layout/Navtabs'; -import GlobalSearchContainer from 'views/layout/GlobalSearchContainer'; +import Navbar from 'views/layout/Navbar'; import Notification from 'views/components/notfications/Notification'; import ErrorBoundary from 'views/errors/ErrorBoundary'; import ErrorPage from 'views/errors/ErrorPage'; import ApiError from 'views/errors/ApiError'; import { trackPageView } from 'bootstrapping/matomo'; import { isIOS } from 'bootstrapping/browser'; -import Logo from 'img/nusmods-logo.svg'; import type { Dispatch } from 'types/redux'; import type { State } from 'types/state'; import type { Actions } from 'types/actions'; @@ -34,8 +31,6 @@ import LoadingSpinner from './components/LoadingSpinner'; import FeedbackModal from './components/FeedbackModal'; import KeyboardShortcuts from './components/KeyboardShortcuts'; -import styles from './AppShell.scss'; - /** * Fetch module list on mount. */ @@ -136,23 +131,9 @@ const AppShell: FC = ({ children }) => { /> - +
- -
{isModuleListReady ? ( }> diff --git a/website/src/views/components/LinkModuleCodes.tsx b/website/src/views/components/LinkModuleCodes.tsx index ebcfa95fff..43cac6a438 100644 --- a/website/src/views/components/LinkModuleCodes.tsx +++ b/website/src/views/components/LinkModuleCodes.tsx @@ -29,7 +29,8 @@ export const LinkModuleCodesComponent: React.FC = (props) => { const tooltip = ( <> - {module.title} {' '} + {module.title} + ); diff --git a/website/src/views/components/SearchkitSearchBox.scss b/website/src/views/components/SearchkitSearchBox.scss index b75043cc71..d4ae96c44f 100644 --- a/website/src/views/components/SearchkitSearchBox.scss +++ b/website/src/views/components/SearchkitSearchBox.scss @@ -12,14 +12,12 @@ ); @include media-breakpoint-down(sm) { - z-index: $module-finder-search-z-index-sm; - padding: 0 0 $navtab-shadow-height; + padding: 0 0 $navbar-shadow-height; margin: -1rem (-$grid-gutter-width / 2) 0; - border-top: 1px solid var(--gray-lighter); background: linear-gradient( to bottom, - var(--body-bg) calc(100% - #{$navtab-shadow-height}), - var(--navtab-shadow) calc(100% - #{$navtab-shadow-height}), + var(--body-bg) calc(100% - #{$navbar-shadow-height}), + var(--navbar-shadow) calc(100% - #{$navbar-shadow-height}), rgba(#000, 0) ); } diff --git a/website/src/views/components/SemesterBadge.scss b/website/src/views/components/SemesterBadge.scss index 49af7584ee..b12d95dc03 100644 --- a/website/src/views/components/SemesterBadge.scss +++ b/website/src/views/components/SemesterBadge.scss @@ -2,7 +2,7 @@ .semesters { :global(.badge) { - margin-right: 0.2rem; + margin-left: 0.2rem; border-radius: 1px; } } diff --git a/website/src/views/components/SideMenu.scss b/website/src/views/components/SideMenu.scss index 83d7c8e2d9..9e086ad97b 100644 --- a/website/src/views/components/SideMenu.scss +++ b/website/src/views/components/SideMenu.scss @@ -67,7 +67,7 @@ $offset-top: $navbar-height + 1rem; transform: translateY($closed-menu-translation); transition: transform $animation-duration, box-shadow $animation-duration; &.isOpen { - box-shadow: 0 -10px 20px var(--navtab-shadow); + box-shadow: 0 -10px 20px var(--navbar-shadow); transform: translateY(0); } } diff --git a/website/src/views/components/filters/DropdownListFilters.tsx b/website/src/views/components/filters/DropdownListFilters.tsx index 48603e2b22..9062faab1c 100644 --- a/website/src/views/components/filters/DropdownListFilters.tsx +++ b/website/src/views/components/filters/DropdownListFilters.tsx @@ -125,7 +125,7 @@ const DesktopFilter: FC = ({ allItems, onSelectItem, showCount, pl key={key} {...getItemProps({ item: key, - className: classnames('dropdown-item form-check', { + className: classnames('dropdown-item form-check', styles.formCheckItem, { 'dropdown-selected': index === highlightedIndex, [styles.enabled]: selected, }), diff --git a/website/src/views/components/filters/styles.scss b/website/src/views/components/filters/styles.scss index 8a72442873..04ac774ae5 100644 --- a/website/src/views/components/filters/styles.scss +++ b/website/src/views/components/filters/styles.scss @@ -71,6 +71,10 @@ $h-padding: $grid-gutter-width / 2; margin-bottom: 0; } + .formCheckItem { + padding-left: 1.825rem; + } + .label { white-space: normal; } diff --git a/website/src/views/components/module-info/AddModuleDropdown.scss b/website/src/views/components/module-info/AddModuleDropdown.scss index 045bfa5563..2ca522a043 100644 --- a/website/src/views/components/module-info/AddModuleDropdown.scss +++ b/website/src/views/components/module-info/AddModuleDropdown.scss @@ -1,4 +1,4 @@ -@import "~styles/utils/modules-entry"; +@import '~styles/utils/modules-entry'; .buttonGroup { :global(.btn) { @@ -20,15 +20,20 @@ } } -.dropdownItem { - text-align: center; - white-space: normal; +.dropdownMenu { + width: 100%; - br { - display: none; - } + .dropdownItemContents { + width: 100%; + text-align: center; + white-space: normal; - strong { - font-size: 1em; + br { + display: none; + } + + strong { + font-size: 1em; + } } } diff --git a/website/src/views/components/module-info/AddModuleDropdown.tsx b/website/src/views/components/module-info/AddModuleDropdown.tsx index e9d9d50863..f3599b1d7d 100644 --- a/website/src/views/components/module-info/AddModuleDropdown.tsx +++ b/website/src/views/components/module-info/AddModuleDropdown.tsx @@ -134,7 +134,7 @@ export class AddModuleDropdownComponent extends PureComponent {
{otherSemesters.map((semester, index) => ( @@ -142,12 +142,14 @@ export class AddModuleDropdownComponent extends PureComponent { {...getItemProps({ item: semester })} type="button" key={semester} - className={classnames('dropdown-item', styles.dropdownItem, { + className={classnames('dropdown-item', { 'dropdown-selected': index === highlightedIndex, })} onClick={() => this.onSelect(semester)} > - {this.buttonLabel(semester)} + + {this.buttonLabel(semester)} + ))}
diff --git a/website/src/views/layout/Footer.scss b/website/src/views/layout/Footer.scss index dc47c738ae..590c7d4e12 100644 --- a/website/src/views/layout/Footer.scss +++ b/website/src/views/layout/Footer.scss @@ -3,7 +3,8 @@ .footer { composes: text-muted from global; padding: 4rem 0 4rem 0; // For Microsoft Edge 18 - padding: 4rem env(safe-area-inset-right) calc(env(safe-area-inset-bottom) + 4rem) env(safe-area-inset-left); + padding: 4rem env(safe-area-inset-right) calc(env(safe-area-inset-bottom) + 4rem) + env(safe-area-inset-left); margin-top: 4rem; font-size: 0.85rem; text-align: center; @@ -25,22 +26,14 @@ } .footerContainer { + max-width: 75rem; padding: 0 15px; + margin: auto; } @include media-breakpoint-up(sm) { text-align: left; } - - @include media-breakpoint-up(md) { - padding-left: #{$side-nav-width-md}; // For Microsoft Edge 18 - padding-left: calc(env(safe-area-inset-left) + #{$side-nav-width-md}); - } - - @include media-breakpoint-up(xl) { - padding-left: #{$side-nav-width-lg}; // For Microsoft Edge 18 - padding-left: calc(env(safe-area-inset-left) + #{$side-nav-width-lg}); - } } .footerLinks { diff --git a/website/src/views/layout/GlobalSearch.scss b/website/src/views/layout/GlobalSearch.scss index 2468a05ccb..26a7224c14 100644 --- a/website/src/views/layout/GlobalSearch.scss +++ b/website/src/views/layout/GlobalSearch.scss @@ -1,120 +1,78 @@ -@import '~styles/utils/modules-entry'; -$icon-size: $font-size-lg; -$input-width: 2rem; -$input-width-open: 21rem; -$input-width-open-lg: 34rem; -$container-padding: 0.5rem; +@import './nav-constants'; + +$results-width-open: 21rem; $dropdown-height: 40rem; +$icon-horizontal-padding: ($container-height - $navbar-icon-size) / 2; .container { composes: btn-svg from global; position: relative; - height: 100%; - padding-right: $container-padding; - margin-left: auto; - border-right: 1px solid var(--gray-lighter); - font-size: 0.9rem; - - &:hover .icon { - color: theme-color(); - } + width: 100%; + max-width: $results-width-open; + margin: $container-vertical-margin $container-horizontal-margin; } -$icon-dist: $input-width-open - $input-width; -$icon-dist-lg: $input-width-open-lg - $input-width; - .icon { - position: absolute; - right: $icon-dist + $container-padding; - width: $icon-size; - height: $icon-size; - transform: translate($icon-dist - 0.25rem); - transition: all $desktop-entering-duration $material-deceleration-curve; - - @include media-breakpoint-up(lg) { - right: $icon-dist-lg + $container-padding; - transform: translate($icon-dist-lg - 0.25rem); - } -} - -.iconOpen { - color: theme-color(); - transform: translate(0); + width: $navbar-icon-size; + height: $navbar-icon-size; + margin: 0 $icon-horizontal-padding; + color: var(--gray-mid); } .input { - position: absolute; - right: $input-width-open + $container-padding; - opacity: 0; - width: $input-width; - height: 100%; - padding-left: $input-width; - border: 0; + // Extend input to the left of icon so that the icon is clickable. + $overhang: $navbar-icon-size + $icon-horizontal-padding * 2; + + padding-left: $overhang; + margin-left: -$overhang; + border-radius: $corner-radius; font-size: $font-size-s; - color: var(--body-color); background: transparent; - cursor: pointer; - transform: translate($input-width-open); - transition: transform $desktop-entering-duration $material-deceleration-curve, - opacity $desktop-entering-duration $material-deceleration-curve 75ms; &:focus { - outline: none; background: transparent; - box-shadow: none; } } -.openWidth { - width: $input-width-open; - - @include media-breakpoint-up(lg) { - width: $input-width-open-lg; - } -} - -.inputOpen { - composes: openWidth; - right: 0; - opacity: 1; - cursor: auto; - transform: translate(0); -} - .selectListContainer { - position: absolute; - top: $navbar-height - 0.5rem; right: 0; - z-index: $module-select-z-index; - border: $input-btn-border-width solid $input-border-color; - border-width: 0 $input-btn-border-width $input-btn-border-width; - border-radius: 0 0 $btn-border-radius $btn-border-radius; + left: auto; + width: $results-width-open; + padding: 0; // Don't interfere with child .selectList's padding + margin-top: $container-vertical-margin; + font-size: $font-size-sm; color: var(--body-color); - background: var(--body-bg); } .selectList { - composes: list-unstyled scrollable-y from global; - max-height: $dropdown-height; - @media (max-height: $dropdown-height + 5rem) { - max-height: calc(90vh - #{$navbar-height}); + composes: scrollable-y from global; + // Prevent dropdown (including its bottom shadow) from going below the screen's bottom edge. + // Use Min instead of min to use CSS's min instead of Sass's, which doesn't + // accept a calc-ed value as a parameter. + // See: https://css-tricks.com/when-sass-and-new-css-features-collide/#the-solution + // stylelint-disable-next-line function-name-case + max-height: Min(calc(100vh - #{$navbar-height} - 6px), $dropdown-height); + padding: 0 0.4rem 0.4rem 0.4rem; + + :global(.dropdown-divider) { + margin-bottom: 0.1rem; } } -.item { - composes: openWidth; +.selectHeader { + position: sticky; + top: 0; display: flex; justify-content: space-between; - padding: 0.6rem $container-padding; - cursor: pointer; + align-items: center; + padding: 0.4rem 0 0.4rem 0.75rem; + font-size: 0.95rem; + background-color: var(--body-bg); } -.selectHeader { - composes: item; - flex-direction: row-reverse; - justify-content: space-between; - border-bottom: 1px solid var(--gray-lighter); - font-size: 0.95rem; +.headerAction { + composes: dropdown-item from global; + width: unset; &.selected { > :global(.btn-svg) { @@ -127,9 +85,19 @@ $icon-dist-lg: $input-width-open-lg - $input-width; } } -.headerName { - text-transform: uppercase; - letter-spacing: 0.06em; +.headerTitle { + display: flex; + align-items: center; + + .headerName { + font-weight: bold; + } + + .headerIcon { + width: 1.1rem; + height: 1.1rem; + margin-right: 0.4rem; + } } .svg { @@ -139,21 +107,23 @@ $icon-dist-lg: $input-width-open-lg - $input-width; } .option { - composes: item; + composes: dropdown-item from global; - &.selected { - color: theme-color(); - background: var(--gray-lightest); + .title { + white-space: break-spaces; } +} - .semesters { - flex: 0 0 auto; - margin-right: 0.4rem; - } +.semesters { + composes: dropdown-item-right-content from global; + flex: 0 0 auto; +} + +.selected { + background-color: var(--gray-lightest); } .noResults { - composes: openWidth; text-align: center; p { diff --git a/website/src/views/layout/GlobalSearch.tsx b/website/src/views/layout/GlobalSearch.tsx index c12f36fa25..4ea859771c 100644 --- a/website/src/views/layout/GlobalSearch.tsx +++ b/website/src/views/layout/GlobalSearch.tsx @@ -2,7 +2,7 @@ import { Component, Fragment } from 'react'; import { omit, stubString } from 'lodash'; import Downshift, { ChildrenFunction, DownshiftState, StateChangeOptions } from 'downshift'; import classnames from 'classnames'; -import { ChevronRight, HelpCircle as Help, Search } from 'react-feather'; +import { BookOpen, ChevronRight, HelpCircle as Help, Map, Search } from 'react-feather'; import { highlight } from 'utils/react'; import { ModuleCondensed } from 'types/modules'; @@ -29,55 +29,36 @@ type Props = { }; type State = { - isOpen: boolean; inputValue: string; }; -const PLACEHOLDER = 'Search modules & venues. Try "GER" or "LT".'; +const PLACEHOLDER = 'Search NUSMods'; class GlobalSearch extends Component { input: HTMLInputElement | null = null; state = { - isOpen: false, inputValue: '', }; - onOpen = () => { - this.setState({ isOpen: true }); - }; - - onClose = () => { - this.setState({ - isOpen: false, - inputValue: '', - }); - - if (this.input) this.input.blur(); - }; - - onOuterClick = () => { + handleOuterClick = () => { // Preserve input value (if present) after user clicks outside. if (this.state.inputValue) { this.setState({ - isOpen: true, // Cannot use prevState as prevState.inputValue will be empty string // instead of the (non-empty) this.state.inputValue. // eslint-disable-next-line react/no-access-state-in-setstate inputValue: this.state.inputValue, }); - - if (this.input) this.input.blur(); - } else { - this.onClose(); } + this.input?.blur(); }; - onInputValueChange = (newInputValue: string) => { + handleInputValueChange = (newInputValue: string) => { this.setState({ inputValue: newInputValue }); }; - onChange = (item: SearchItem | null) => { + handleChange = (item: SearchItem | null) => { if (item) { const { onSelectModule, onSelectVenue, onSearch } = this.props; @@ -96,7 +77,7 @@ class GlobalSearch extends Component { } } - this.onClose(); + this.input?.blur(); }; stateReducer = (state: DownshiftState, changes: StateChangeOptions) => { @@ -111,11 +92,12 @@ class GlobalSearch extends Component { // Downshift attaches label for us, so we can ignore ESLint here /* eslint-disable jsx-a11y/label-has-for */ renderDropdown: ChildrenFunction = ({ + isOpen, + openMenu, getLabelProps, getInputProps, getItemProps, getMenuProps, - isOpen, inputValue, highlightedIndex, }) => { @@ -123,7 +105,7 @@ class GlobalSearch extends Component { // selection to be lost const searchForm = ( - + @@ -132,18 +114,17 @@ class GlobalSearch extends Component { this.input = r; ComponentMap.globalSearchInput = r; }} - className={classnames(styles.input, { [styles.inputOpen]: isOpen })} + className={classnames('form-control', styles.input)} + onFocus={() => openMenu()} {...getInputProps({ placeholder: PLACEHOLDER })} - onFocus={this.onOpen} /> ); const searchResults = this.props.getResults(inputValue); - const hasFocus = document.activeElement === this.input; // 1. Search is not active - just show the search form - if (!searchResults || !inputValue || !hasFocus) { + if (!isOpen || !searchResults || !inputValue) { return
{searchForm}
; } @@ -158,8 +139,11 @@ class GlobalSearch extends Component {
{searchForm} -
-
+ {/* Wrap select list in absolute-positioned container to fix macOS Safari scrolling perf */} +
+

@@ -212,22 +196,26 @@ class GlobalSearch extends Component { {searchForm} {/* Wrap select list in absolute-positioned container to fix macOS Safari scrolling perf */} -

+
{hasModules && ( <> -
- +
+ + + Modules + + View All - Modules
{modules.map((module, index) => ( @@ -240,7 +228,9 @@ class GlobalSearch extends Component { [styles.selected]: highlightedIndex === index + 1, })} > - {highlight(`${module.moduleCode} ${module.title}`, tokens)} + + {highlight(`${module.moduleCode} ${module.title}`, tokens)} +
@@ -248,20 +238,26 @@ class GlobalSearch extends Component { )} + {hasModules && hasVenues &&
} + {hasVenues && ( <> -
- +
+ + + Venues + + View All - Venues
{venues.map((venue, index) => ( @@ -286,14 +282,13 @@ class GlobalSearch extends Component { }; render() { - const { isOpen, inputValue } = this.state; + const { inputValue } = this.state; return ( { userEvent.type(getByRole('textbox'), '1 '); expect(getAllByRole('option').map((elem) => elem.textContent)).toMatchInlineSnapshot(` Array [ - "View All Modules", + "View All ", "AA1010 TestSem 1", "AB1010 TestSem 1", "AC1010 TestSem 1", "AD1010 TestSem 1", "AE1010 TestSem 1", "AF1010 TestSem 1", - "View All Venues", + "View All ", "AA-1", "BB-1", "CC-1", @@ -122,36 +122,36 @@ describe('GlobalSearchContainer', () => { }); userEvent.type(getByRole('textbox'), '1 '); expect(getAllByRole('option').map((elem) => elem.textContent)).toMatchInlineSnapshot(` - Array [ - "View All Venues", - "AA-1", - "BB-1", - "CC-1", - "DD-1", - "EE-1", - "FF-1", - "GG-1", - "HH-1", - "II-1", - "JJ-1", - "KK-1", - "LL-1", - "MM-1", - "NN-1", - "OO-1", - "PP-1", - "QQ-1", - "RR-1", - "SS-1", - "TT-1", - "UU-1", - "VV-1", - "WW-1", - "XX-1", - "YY-1", - "ZZ-1", - ] - `); + Array [ + "View All ", + "AA-1", + "BB-1", + "CC-1", + "DD-1", + "EE-1", + "FF-1", + "GG-1", + "HH-1", + "II-1", + "JJ-1", + "KK-1", + "LL-1", + "MM-1", + "NN-1", + "OO-1", + "PP-1", + "QQ-1", + "RR-1", + "SS-1", + "TT-1", + "UU-1", + "VV-1", + "WW-1", + "XX-1", + "YY-1", + "ZZ-1", + ] + `); }); test('shows at most 10 choices when there are many modules', () => { @@ -161,7 +161,7 @@ describe('GlobalSearchContainer', () => { userEvent.type(getByRole('textbox'), '1 '); expect(getAllByRole('option').map((elem) => elem.textContent)).toMatchInlineSnapshot(` Array [ - "View All Modules", + "View All ", "AA1010 TestSem 1", "AB1010 TestSem 1", "AC1010 TestSem 1", @@ -170,7 +170,7 @@ describe('GlobalSearchContainer', () => { "AF1010 TestSem 1", "AG1010 TestSem 1", "AH1010 TestSem 1", - "View All Venues", + "View All ", "AA-1", "BB-1", ] @@ -182,9 +182,9 @@ describe('GlobalSearchContainer', () => { userEvent.type(getByRole('textbox'), 'AA'); expect(getAllByRole('option').map((elem) => elem.textContent)).toMatchInlineSnapshot(` Array [ - "View All Modules", + "View All ", "AA1010 TestSem 1", - "View All Venues", + "View All ", "AA-1", ] `); @@ -198,7 +198,7 @@ describe('GlobalSearchContainer', () => { userEvent.type(getByRole('textbox'), '1010'); expect(getAllByRole('option').map((elem) => elem.textContent)).toMatchInlineSnapshot(` Array [ - "View All Modules", + "View All ", "AA1010 TestSem 1", "AB1010 TestSem 1", "AC1010 TestSem 1", @@ -280,79 +280,79 @@ describe('GlobalSearchContainer', () => { userEvent.type(getByRole('textbox'), 'venue'); expect(getAllByRole('option').map((elem) => elem.textContent)).toMatchInlineSnapshot(` - Array [ - "View All Venues", - "Venue 0", - "Venue 1", - "Venue 2", - "Venue 3", - "Venue 4", - "Venue 5", - "Venue 6", - "Venue 7", - "Venue 8", - "Venue 9", - "Venue 10", - "Venue 11", - "Venue 12", - "Venue 13", - "Venue 14", - "Venue 15", - "Venue 16", - "Venue 17", - "Venue 18", - "Venue 19", - "Venue 20", - "Venue 21", - "Venue 22", - "Venue 23", - "Venue 24", - "Venue 25", - "Venue 26", - "Venue 27", - "Venue 28", - "Venue 29", - "Venue 30", - "Venue 31", - "Venue 32", - "Venue 33", - "Venue 34", - "Venue 35", - "Venue 36", - "Venue 37", - "Venue 38", - "Venue 39", - "Venue 40", - "Venue 41", - "Venue 42", - "Venue 43", - "Venue 44", - "Venue 45", - "Venue 46", - "Venue 47", - "Venue 48", - "Venue 49", - "Venue 50", - "Venue 51", - "Venue 52", - "Venue 53", - "Venue 54", - "Venue 55", - "Venue 56", - "Venue 57", - "Venue 58", - "Venue 59", - "Venue 60", - "Venue 61", - "Venue 62", - "Venue 63", - "Venue 64", - "Venue 65", - "Venue 66", - "Venue 67", - "Venue 68", - "Venue 69", - ] - `); + Array [ + "View All ", + "Venue 0", + "Venue 1", + "Venue 2", + "Venue 3", + "Venue 4", + "Venue 5", + "Venue 6", + "Venue 7", + "Venue 8", + "Venue 9", + "Venue 10", + "Venue 11", + "Venue 12", + "Venue 13", + "Venue 14", + "Venue 15", + "Venue 16", + "Venue 17", + "Venue 18", + "Venue 19", + "Venue 20", + "Venue 21", + "Venue 22", + "Venue 23", + "Venue 24", + "Venue 25", + "Venue 26", + "Venue 27", + "Venue 28", + "Venue 29", + "Venue 30", + "Venue 31", + "Venue 32", + "Venue 33", + "Venue 34", + "Venue 35", + "Venue 36", + "Venue 37", + "Venue 38", + "Venue 39", + "Venue 40", + "Venue 41", + "Venue 42", + "Venue 43", + "Venue 44", + "Venue 45", + "Venue 46", + "Venue 47", + "Venue 48", + "Venue 49", + "Venue 50", + "Venue 51", + "Venue 52", + "Venue 53", + "Venue 54", + "Venue 55", + "Venue 56", + "Venue 57", + "Venue 58", + "Venue 59", + "Venue 60", + "Venue 61", + "Venue 62", + "Venue 63", + "Venue 64", + "Venue 65", + "Venue 66", + "Venue 67", + "Venue 68", + "Venue 69", + ] + `); }); }); diff --git a/website/src/views/layout/NavDropdown.scss b/website/src/views/layout/NavDropdown.scss new file mode 100644 index 0000000000..8b2505f440 --- /dev/null +++ b/website/src/views/layout/NavDropdown.scss @@ -0,0 +1,46 @@ +@import '~styles/utils/modules-entry'; +@import './nav-constants'; + +.container { + position: relative; + display: flex; + margin: $container-vertical-margin $container-horizontal-margin; +} + +.toggle { + position: relative; + // Ensure .toggle is a circle by setting width. Its height should already be + // equal to $container-height. + width: $container-height; + padding: 0; + border-radius: $corner-radius; + font-size: 0.9rem; + + &:hover { + background-color: var(--gray-lightest); + } + + &:focus { + border-color: theme-color(primary); + } +} + +.buttonIcon { + width: $navbar-icon-size; + height: $navbar-icon-size; + margin: 0; + color: var(--gray-mid); +} + +.dropdownMenu { + right: 0; + left: auto; + width: 16rem; + // Prevent dropdown (including its bottom shadow) from going below the screen's bottom edge. + max-height: calc(100vh - #{$navbar-height} - 6px); + margin-top: $container-vertical-margin; + + @include media-breakpoint-up(md) { + font-size: $font-size-sm; + } +} diff --git a/website/src/views/layout/NavDropdown.tsx b/website/src/views/layout/NavDropdown.tsx new file mode 100644 index 0000000000..72db34c9ae --- /dev/null +++ b/website/src/views/layout/NavDropdown.tsx @@ -0,0 +1,110 @@ +import classnames from 'classnames'; +import Downshift, { ChildrenFunction } from 'downshift'; +import type { FC } from 'react'; +import { + Calendar, + Droplet, + ExternalLink as ExternalLinkIcon, + Heart, + Menu, + Settings, + Star, + Trello, +} from 'react-feather'; +import { useSelector } from 'react-redux'; +import { NavLink } from 'react-router-dom'; +import NUSModerator from 'nusmoderator'; + +import ExternalLink from 'views/components/ExternalLink'; +import { preload as preloadContribute } from 'views/contribute/ContributeContainer'; +import type { State } from 'types/state'; +import weekText from 'utils/weekText'; + +import styles from './NavDropdown.scss'; + +// Only compute this on page load. +const { year } = NUSModerator.academicCalendar.getAcadWeekInfo(new Date()); +const baseYearNumber = parseInt(year.slice(0, 2), 10); + +const NavDropdown: FC = () => { + const beta = useSelector(({ settings }: State) => settings.beta); + + const renderDropdown: ChildrenFunction = ({ + isOpen, + getMenuProps, + toggleMenu, + closeMenu, + }) => { + const itemProps = { + className: 'dropdown-item', + onClick: () => closeMenu(), + }; + return ( +
+ + +
+ {beta && ( + <> + + + Planner + Beta + +
+ + )} + + + Settings + + + + Contribute + +
+ + + NUS Business + + + + + NUSWhispers + + +
+
{weekText}
+ + + Academic Calendar + + +
+
+ ); + }; + + return {renderDropdown}; +}; + +export default NavDropdown; diff --git a/website/src/views/layout/Navbar.scss b/website/src/views/layout/Navbar.scss new file mode 100644 index 0000000000..97cfb5b119 --- /dev/null +++ b/website/src/views/layout/Navbar.scss @@ -0,0 +1,89 @@ +@import '~styles/utils/modules-entry.scss'; + +// Keep padding at whole number, 0.875rem = 14px +// See: https://github.com/nusmodifications/nusmods/pull/1567 +$logo-vert-padding: 0.875rem; + +.navbarWrapper { + position: sticky; + top: 0; + z-index: $navbar-z-index; + display: flex; + justify-content: space-between; + padding: 0; // For Microsoft Edge 18 + padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left); + background: var(--body-bg); + + // Fake shadow - don't use box shadow as the sides will be lighter + &::after { + content: ''; + position: absolute; + right: 0; + bottom: -$navbar-shadow-height; + left: 0; + height: $navbar-shadow-height; + background: linear-gradient(to bottom, var(--navbar-shadow), rgba(#000, 0)); + pointer-events: none; + } +} + +.brandLogo { + // Explicit width so the logo will not expand + // to occupy the entire navbar + width: 11rem; // Eyeballed number + height: 100%; + padding: $logo-vert-padding $grid-gutter-width/2 $logo-vert-padding $grid-gutter-width; +} + +.navLeft { + display: flex; + flex: 1 0 0; + height: $navbar-height; +} + +.navCenter { + height: $navbar-height; +} + +.navRight { + display: flex; + flex: 1 0 0; + justify-content: flex-end; + height: $navbar-height; + padding-right: $grid-gutter-width; +} + +.weekText { + padding-left: $grid-gutter-width/2; + font-size: $font-size-xs; + line-height: 1.15; + text-align: right; +} + +@include media-breakpoint-down(sm) { + .navbarWrapper { + top: -$navbar-height; + flex-wrap: wrap; + } + + .navLeft { + flex: unset; + order: 0; + } + + .navCenter { + order: 2; + width: 100%; + } + + .navRight { + flex: unset; + order: 1; + padding-right: $grid-gutter-width / 2; + } + + .brandLogo { + width: 9rem; // Eyeballed number + padding: $logo-vert-padding 0 $logo-vert-padding $grid-gutter-width/2; + } +} diff --git a/website/src/views/layout/Navtabs.test.tsx b/website/src/views/layout/Navbar.test.tsx similarity index 69% rename from website/src/views/layout/Navtabs.test.tsx rename to website/src/views/layout/Navbar.test.tsx index 4fc7654ad6..eef9012f60 100644 --- a/website/src/views/layout/Navtabs.test.tsx +++ b/website/src/views/layout/Navbar.test.tsx @@ -5,9 +5,18 @@ import React from 'react'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import reducers from 'reducers'; +import { mockDom, mockDomReset } from 'test-utils/mockDom'; import { initAction } from 'test-utils/redux'; -import Navtabs from './Navtabs'; +import Navbar from './Navbar'; + +jest.mock('nusmoderator', () => ({ + academicCalendar: { + getAcadWeekInfo: jest.fn(() => ({ + year: '10/11', + })), + }, +})); const relevantStoreContents = { app: { activeSemester: 1 }, @@ -27,40 +36,54 @@ function make(storeOverrides: Partial = {}) { render( - , + , , ); } -describe(Navtabs, () => { - test('should render into nav element', () => { +describe(Navbar, () => { + beforeEach(() => { + mockDom(); + }); + + afterEach(() => { + mockDomReset(); + }); + + test('should render nav links', () => { make(); expect(screen.getAllByRole('link').map((elem) => elem.textContent)).toMatchInlineSnapshot(` Array [ + "", "Today", "Timetable", "Modules", "Venues", "Settings", "Contribute", - "Whispers", + "NUS Business", + "NUSWhispers", + "Academic Calendar", ] `); }); - test('should show beta tabs if beta is true', () => { + test('should show beta nav links if beta is true', () => { make({ settings: { beta: true } }); expect(screen.getAllByRole('link').map((elem) => elem.textContent)).toMatchInlineSnapshot(` Array [ + "", "Today", "Timetable", "Modules", "Venues", - "Planner", + "PlannerBeta", "Settings", "Contribute", - "Whispers", + "NUS Business", + "NUSWhispers", + "Academic Calendar", ] `); }); diff --git a/website/src/views/layout/Navbar.tsx b/website/src/views/layout/Navbar.tsx new file mode 100644 index 0000000000..346e29e350 --- /dev/null +++ b/website/src/views/layout/Navbar.tsx @@ -0,0 +1,32 @@ +import type { FC } from 'react'; +import { NavLink } from 'react-router-dom'; + +import ErrorBoundary from 'views/errors/ErrorBoundary'; +import Logo from 'img/nusmods-logo.svg'; + +import GlobalSearchContainer from './GlobalSearchContainer'; +import NavDropdown from './NavDropdown'; +import Navtabs from './Navtabs'; + +import styles from './Navbar.scss'; + +const Navbar: FC = () => ( +
+
+ + + +
+
+ +
+
+ + + + +
+
+); + +export default Navbar; diff --git a/website/src/views/layout/Navtabs.scss b/website/src/views/layout/Navtabs.scss index 6fa2926104..2af602c85b 100644 --- a/website/src/views/layout/Navtabs.scss +++ b/website/src/views/layout/Navtabs.scss @@ -1,146 +1,65 @@ -@import '~styles/utils/modules-entry.scss'; +@import './nav-constants'; -/* - For breakpoint-md (tablets), show the links as compact buttons with labels below icons. - For breakpoint-lg (desktop), show the links as traditional link with icon to the left. -*/ .nav { - position: fixed; - top: $navbar-height; - right: 0; - left: 0; - z-index: $navtabs-z-index; - // See site.scss for width on md and lg sizes display: flex; - justify-content: space-around; + justify-content: center; align-items: stretch; height: $navtab-height; - - @include media-breakpoint-up(md) { - flex-direction: column; - justify-content: flex-start; - margin-top: 0.5rem; - box-shadow: none; - } - - @include media-breakpoint-up(xl) { - margin-top: 1rem; - } - - @include media-breakpoint-down(sm) { - position: sticky; - top: 0; - background-color: var(--body-bg); - - // Fake shadow - can't use box shadow because they will appear on top of the - // top nav bar - &::after { - content: ''; - position: absolute; - right: 0; - bottom: -$navtab-shadow-height; - left: 0; - height: $navtab-shadow-height; - background: linear-gradient(to bottom, var(--navtab-shadow), rgba(#000, 0)); - pointer-events: none; - } - } + margin: 0 auto; } .link { - position: relative; - flex: 1 0 auto; + display: flex; + flex-grow: 1; + justify-content: center; + align-items: center; + width: 4rem; + margin: 0 0.2rem; + border-top: 0.2rem solid transparent; + border-bottom: 0.2rem solid transparent; line-height: 1.6; - text-align: center; color: var(--gray-mid); - transition: color 0.15s; + transition: color $desktop-entering-duration $material-deceleration-curve, + border-bottom-color $desktop-entering-duration $material-deceleration-curve; &.linkActive { + border-bottom-color: theme-color(primary); color: theme-color(primary); } - &:hover .title { - text-decoration: underline; - } - - @include media-breakpoint-down(sm) { - display: flex; - justify-content: center; - align-items: center; - - svg { - margin-bottom: 0.1rem; + &:hover { + &:not(.linkActive) { + border-bottom-color: rgba(theme-color(primary), 0.3); } } - @include media-breakpoint-up(md) { - padding: 0.8rem 0.5rem; - } - - @include media-breakpoint-up(xl) { - display: flex; - align-items: center; - padding: 0.8rem 0 0.8rem 2rem; - text-align: left; + svg { + flex: 0 0 auto; + width: $navbar-icon-size; + height: $navbar-icon-size; + margin-bottom: 0.1rem; } } .title { display: none; + margin-left: 0.4rem; font-size: 0.7rem; - - @include media-breakpoint-up(md) { - display: block; - } - - @include media-breakpoint-up(xl) { - margin-left: 0.4rem; - font-size: 1rem; - } -} - -.hiddenOnMobile { - @include media-breakpoint-down(sm) { - display: none; - } + font-size: 1rem; } -.divider { - $size: 70%; - $margin: 0.3rem; - - composes: hiddenOnMobile; - opacity: 0.6; - flex: 0 0 1px; - margin: $margin ((100% - $size) / 2) 0; - background: var(--gray-lighter); - - @include media-breakpoint-up(xl) { - margin: $margin 1rem $margin 1.4rem; +@include media-breakpoint-down(sm) { + .link { + min-width: unset; } } -.updateDot { - position: absolute; - top: 0.5rem; - width: 0.5rem; - height: 0.5rem; - padding: 0; - margin-left: 1rem; - border-radius: 50%; - font-size: $font-size-xs; - line-height: 1.2; - background: theme-color(success); - - :global { - animation: zoomIn 0.4s; +@include media-breakpoint-up(lg) { + .link { + min-width: 7em; } - @include media-breakpoint-up(md) { - right: 1.4rem; - } - - @include media-breakpoint-up(xl) { - display: none; + .title { + display: block; } } diff --git a/website/src/views/layout/Navtabs.tsx b/website/src/views/layout/Navtabs.tsx index e9ddd0f6ec..7857479f45 100644 --- a/website/src/views/layout/Navtabs.tsx +++ b/website/src/views/layout/Navtabs.tsx @@ -1,14 +1,11 @@ import type { FC } from 'react'; import { useSelector } from 'react-redux'; import { NavLink } from 'react-router-dom'; -import classnames from 'classnames'; -import { BookOpen, Calendar, Clock, Heart, Map, Settings, Star, Trello } from 'react-feather'; +import { BookOpen, Calendar, Clock, Map } from 'react-feather'; -import ExternalLink from 'views/components/ExternalLink'; import { timetablePage } from 'views/routes/paths'; import { preload as preloadToday } from 'views/today/TodayContainer'; import { preload as preloadVenues } from 'views/venues/VenuesContainer'; -import { preload as preloadContribute } from 'views/contribute/ContributeContainer'; import type { State } from 'types/state'; import styles from './Navtabs.scss'; @@ -17,7 +14,6 @@ export const NAVTAB_HEIGHT = 48; const Navtabs: FC = () => { const activeSemester = useSelector(({ app }: State) => app.activeSemester); - const beta = useSelector(({ settings }: State) => settings.beta); const tabProps = { className: styles.link, @@ -45,38 +41,6 @@ const Navtabs: FC = () => { Venues - {beta && ( - - - Planner - - )} - - - Settings - - - - Contribute - -
- - - Whispers - ); }; diff --git a/website/src/views/layout/nav-constants.scss b/website/src/views/layout/nav-constants.scss new file mode 100644 index 0000000000..4deb9236eb --- /dev/null +++ b/website/src/views/layout/nav-constants.scss @@ -0,0 +1,8 @@ +@import '~styles/utils/modules-entry'; + +$navbar-icon-size: $font-size-lg; +$container-vertical-margin: 0.3rem; +$container-horizontal-margin: 0.2rem; + +$container-height: $navbar-height - $container-vertical-margin * 2; +$corner-radius: $container-height / 2; diff --git a/website/src/views/modules/ModuleFinderContainer/ModuleFinderContainer.scss b/website/src/views/modules/ModuleFinderContainer/ModuleFinderContainer.scss index ed2e5299fa..24b4433a71 100644 --- a/website/src/views/modules/ModuleFinderContainer/ModuleFinderContainer.scss +++ b/website/src/views/modules/ModuleFinderContainer/ModuleFinderContainer.scss @@ -2,7 +2,6 @@ div.modulesPageContainer { max-width: 75rem; - margin-left: 0; animation-name: $page-entering-animation; } diff --git a/website/src/views/modules/ModulePageContent.scss b/website/src/views/modules/ModulePageContent.scss index 2ba233ab71..66df50dc41 100644 --- a/website/src/views/modules/ModulePageContent.scss +++ b/website/src/views/modules/ModulePageContent.scss @@ -10,7 +10,6 @@ $sticky-top: $navbar-height; .moduleInfoPage { max-width: 80rem; - margin-left: 0; :global { animation-name: $page-entering-animation; diff --git a/website/src/views/planner/ModuleMenu.tsx b/website/src/views/planner/ModuleMenu.tsx index 1ef7830e83..e1b5c99db7 100644 --- a/website/src/views/planner/ModuleMenu.tsx +++ b/website/src/views/planner/ModuleMenu.tsx @@ -44,10 +44,7 @@ const ModuleMenu = memo((props: Props) => { > -
+
{menuItems.map(({ label, className }, itemIndex) => ( )}
diff --git a/website/src/views/today/TodayContainer/TodayContainer.scss b/website/src/views/today/TodayContainer/TodayContainer.scss index 25ada17ef7..5233d52087 100644 --- a/website/src/views/today/TodayContainer/TodayContainer.scss +++ b/website/src/views/today/TodayContainer/TodayContainer.scss @@ -20,7 +20,7 @@ $gutter: 0.75rem; .schedule { position: fixed; - left: $side-nav-width-md + $gutter; + left: $gutter; z-index: 1; overflow: auto; width: $schedule-width; @@ -33,7 +33,7 @@ $gutter: 0.75rem; .mapContainer { position: fixed; right: 0; - left: $side-nav-width-md + $schedule-width + $gutter; + left: $schedule-width + $gutter; &.expanded { z-index: $venue-detail-expanded-z-index; @@ -41,16 +41,6 @@ $gutter: 0.75rem; } } -@include media-breakpoint-up(xl) { - .schedule { - left: $side-nav-width-lg + $gutter; - } - - .mapContainer { - left: $side-nav-width-lg + $schedule-width + $gutter; - } -} - .day { margin-bottom: 4rem; } diff --git a/website/src/views/venues/VenuesContainer.scss b/website/src/views/venues/VenuesContainer.scss index d76414af16..1fe707f587 100644 --- a/website/src/views/venues/VenuesContainer.scss +++ b/website/src/views/venues/VenuesContainer.scss @@ -3,6 +3,8 @@ $gutter: $grid-gutter-width / 2; $sticky-top: $navbar-height + 1rem; $venue-list-width: 16rem; +$venue-list-width-xl: 24rem; +$venue-list-width-xxl: 32rem; // Layout for venues page is // - On md and above - Outlook style two panel UI with the venue list on the left @@ -50,29 +52,32 @@ $venue-list-width: 16rem; .venueDetail { right: 0; // For Microsoft Edge 18 right: env(safe-area-inset-right); - left: calc(#{$venue-list-width + $side-nav-width-md} + #{$gutter}); // For Microsoft Edge 18 - left: calc(env(safe-area-inset-left) + #{$venue-list-width + $side-nav-width-md} + #{$gutter}); + left: calc(#{$venue-list-width} + #{$gutter}); // For Microsoft Edge 18 + left: calc(env(safe-area-inset-left) + #{$venue-list-width} + #{$gutter}); padding: 0 $gutter 0 $gutter; // For Microsoft Edge 18 padding: 0 $gutter env(safe-area-inset-bottom) $gutter; } } @include media-breakpoint-up(xl) { + .venuesList { + width: $venue-list-width-xl; + } + .venueDetail { - left: calc(#{$venue-list-width + $side-nav-width-lg} + #{$gutter}); + left: calc(#{$venue-list-width-xl} + #{$gutter}); // For Microsoft Edge 18 + left: calc(env(safe-area-inset-left) + #{$venue-list-width-xl} + #{$gutter}); } } @media (min-width: 90rem) { .venuesList { - right: 60rem; - left: $side-nav-width-lg; - width: auto; + width: $venue-list-width-xxl; } .venueDetail { - left: auto; - width: 60rem; + left: calc(#{$venue-list-width-xxl} + #{$gutter}); // For Microsoft Edge 18 + left: calc(env(safe-area-inset-left) + #{$venue-list-width-xxl} + #{$gutter}); } }