Просмотр исходного кода

[web] Add box shadows to scrollable part of sidebar (#22400)

* Add box shadows to scrollable part of sidebar

* Fix lint

* Fix `isAtBottom` boolean on Firefox

* Throttle the `scroll` event

* Remove wrong and unnecessary comment

GitOrigin-RevId: 7bcbb7fc860ab32f44203b3ae9ff37a3fed069a8
Antoine Clausse 1 год назад
Родитель
Сommit
e5c97f1963

+ 68 - 55
services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx

@@ -1,4 +1,5 @@
 import { useState } from 'react'
+import classnames from 'classnames'
 import NewProjectButton from '../new-project-button'
 import SidebarFilters from './sidebar-filters'
 import AddAffiliation, { useAddAffiliation } from '../add-affiliation'
@@ -14,6 +15,7 @@ import { NavbarDropdownItemData } from '@/features/ui/components/types/navbar'
 import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
 import { UserProvider } from '@/shared/context/user-context'
 import { AccountMenuItems } from '@/features/ui/components/bootstrap-5/navbar/account-menu-items'
+import { useScrolled } from '@/features/project-list/components/sidebar/use-scroll'
 
 function SidebarDsNav() {
   const { t } = useTranslation()
@@ -31,6 +33,7 @@ function SidebarDsNav() {
   const helpItem = items.find(
     item => item.text === 'help'
   ) as NavbarDropdownItemData
+  const { containerRef, scrolledUp, scrolledDown } = useScrolled()
   return (
     <div
       className="project-list-sidebar-wrapper-react d-none d-md-flex"
@@ -40,82 +43,92 @@ function SidebarDsNav() {
         },
       })}
     >
-      <NewProjectButton id="new-project-button-sidebar" />
-      <div className="project-list-sidebar-scroll">
+      <NewProjectButton
+        id="new-project-button-sidebar"
+        className={scrolledDown ? 'show-shadow' : undefined}
+      />
+      <div className="project-list-sidebar-scroll" ref={containerRef}>
         <SidebarFilters />
         {showAddAffiliationWidget && <hr />}
         <AddAffiliation />
       </div>
-      <div className="project-list-sidebar-survey-wrapper">
-        <SurveyWidget variant="light" />
-      </div>
-      <div className="d-flex gap-3 mb-2">
-        {helpItem && (
-          <Dropdown
-            className="ds-nav-icon-dropdown"
-            onToggle={show => setShowHelpDropdown(show)}
-          >
-            <Dropdown.Toggle role="menuitem" aria-label={t('help')}>
-              <OLTooltip
-                description={t('help')}
-                id="help-icon"
-                overlayProps={{
-                  placement: 'top',
-                  show: showHelpDropdown ? false : undefined,
-                }}
-              >
-                <div>
-                  <MaterialIcon type="help" size="2x" />
-                </div>
-              </OLTooltip>
-            </Dropdown.Toggle>
-            <Dropdown.Menu as="ul" role="menu" align="end">
-              <NavDropdownMenuItems
-                dropdown={helpItem.dropdown}
-                showContactUsModal={showContactUsModal}
-              />
-            </Dropdown.Menu>
-          </Dropdown>
+      <div
+        className={classnames(
+          'ds-nav-sidebar-lower',
+          scrolledUp && 'show-shadow'
         )}
-        {sessionUser && (
-          <>
+      >
+        <div className="project-list-sidebar-survey-wrapper">
+          <SurveyWidget variant="light" />
+        </div>
+        <div className="d-flex gap-3 mb-2">
+          {helpItem && (
             <Dropdown
               className="ds-nav-icon-dropdown"
-              onToggle={show => setShowAccountDropdown(show)}
+              onToggle={show => setShowHelpDropdown(show)}
             >
-              <Dropdown.Toggle role="menuitem" aria-label={t('Account')}>
+              <Dropdown.Toggle role="menuitem" aria-label={t('help')}>
                 <OLTooltip
-                  description={t('Account')}
-                  id="open-account"
+                  description={t('help')}
+                  id="help-icon"
                   overlayProps={{
                     placement: 'top',
-                    show: showAccountDropdown ? false : undefined,
+                    show: showHelpDropdown ? false : undefined,
                   }}
                 >
                   <div>
-                    <MaterialIcon type="person" size="2x" />
+                    <MaterialIcon type="help" size="2x" />
                   </div>
                 </OLTooltip>
               </Dropdown.Toggle>
               <Dropdown.Menu as="ul" role="menu" align="end">
-                <AccountMenuItems
-                  sessionUser={sessionUser}
-                  showSubscriptionLink={showSubscriptionLink}
+                <NavDropdownMenuItems
+                  dropdown={helpItem.dropdown}
+                  showContactUsModal={showContactUsModal}
                 />
               </Dropdown.Menu>
             </Dropdown>
-            <UserProvider>{contactUsModal}</UserProvider>
-          </>
-        )}
-      </div>
-      <div className="ds-nav-ds-link">
-        <a
-          target="_blank"
-          href="https://www.digital-science.com/"
-          rel="noopener"
-        >
-          Digital Science
-        </a>
+          )}
+          {sessionUser && (
+            <>
+              <Dropdown
+                className="ds-nav-icon-dropdown"
+                onToggle={show => setShowAccountDropdown(show)}
+              >
+                <Dropdown.Toggle role="menuitem" aria-label={t('Account')}>
+                  <OLTooltip
+                    description={t('Account')}
+                    id="open-account"
+                    overlayProps={{
+                      placement: 'top',
+                      show: showAccountDropdown ? false : undefined,
+                    }}
+                  >
+                    <div>
+                      <MaterialIcon type="person" size="2x" />
+                    </div>
+                  </OLTooltip>
+                </Dropdown.Toggle>
+                <Dropdown.Menu as="ul" role="menu" align="end">
+                  <AccountMenuItems
+                    sessionUser={sessionUser}
+                    showSubscriptionLink={showSubscriptionLink}
+                  />
+                </Dropdown.Menu>
+              </Dropdown>
+              <UserProvider>{contactUsModal}</UserProvider>
+            </>
+          )}
+        </div>
+        <div className="ds-nav-ds-link">
+          <a
+            target="_blank"
+            href="https://www.digital-science.com/"
+            rel="noopener"
+          >
+            Digital Science
+          </a>
+        </div>
       </div>
       <div
         {...getHandleProps({

+ 39 - 0
services/web/frontend/js/features/project-list/components/sidebar/use-scroll.tsx

@@ -0,0 +1,39 @@
+import { throttle } from 'lodash'
+import { useEffect, useRef, useState } from 'react'
+
+export const useScrolled = () => {
+  const containerRef = useRef<HTMLDivElement>(null)
+  const [scrolledUp, setScrolledUp] = useState(false)
+  const [scrolledDown, setScrolledDown] = useState(false)
+
+  const checkScrollPosition = useRef(
+    throttle(() => {
+      const container = containerRef.current
+      if (!container) {
+        return
+      }
+      setScrolledDown(container.scrollTop > 0)
+      const isAtBottom =
+        Math.abs(
+          // On Firefox, this value happen to be at 1 when scrolled to the bottom
+          container.scrollHeight - container.clientHeight - container.scrollTop
+        ) <= 1
+      setScrolledUp(!isAtBottom)
+    }, 80)
+  ).current
+
+  useEffect(() => {
+    const container = containerRef.current
+    if (!container) {
+      return
+    }
+    checkScrollPosition()
+    container.addEventListener('scroll', checkScrollPosition)
+    return () => {
+      checkScrollPosition.cancel()
+      container.removeEventListener('scroll', checkScrollPosition)
+    }
+  }, [checkScrollPosition])
+
+  return { containerRef, scrolledDown, scrolledUp }
+}

+ 20 - 3
services/web/frontend/stylesheets/bootstrap-5/pages/project-list-ds-nav.scss

@@ -32,13 +32,30 @@
       flex: 0 0 15%;
       max-width: 320px;
       min-width: 200px;
-      padding: var(--spacing-08) var(--spacing-08) 0 var(--spacing-05);
+      padding: var(--spacing-08) 0 0 0;
+
+      .new-project-dropdown {
+        padding: 0 var(--spacing-08) var(--spacing-05) var(--spacing-05);
+
+        &.show-shadow {
+          box-shadow: 0 2px 2px rgb(0 0 0 / 10%);
+        }
+      }
 
       .project-list-sidebar-scroll {
         flex: 1 1 auto;
         overflow: auto;
-        margin: var(--spacing-05) calc(var(--spacing-07) * -1);
-        padding: var(--spacing-05) var(--spacing-07);
+        margin: 0 var(--spacing-02) 0 0;
+        padding: var(--spacing-05) var(--spacing-07) var(--spacing-05)
+          var(--spacing-05);
+      }
+
+      .ds-nav-sidebar-lower {
+        padding: var(--spacing-05) var(--spacing-08) 0 var(--spacing-05);
+
+        &.show-shadow {
+          box-shadow: 0 -2px 2px rgb(0 0 0 / 10%);
+        }
       }
 
       .project-list-sidebar-survey-link {