Explorar el Código

Merge pull request #27715 from overleaf/dp-rail-overflow-2

Add new editor rail overflow menu

GitOrigin-RevId: f93da19a2687c099ece4509c22a374a47e94f5ad
David hace 1 año
padre
commit
c6b031b40b

BIN
services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2


+ 1 - 0
services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs

@@ -22,6 +22,7 @@ export default /** @type {const} */ ([
   'info',
   'integration_instructions',
   'lightbulb',
+  'more_vert',
   'note_add',
   'picture_as_pdf',
   'rate_review',

+ 7 - 1
services/web/frontend/js/features/ide-redesign/components/rail/rail-action-element.tsx

@@ -13,6 +13,7 @@ type RailActionButton = {
   icon: AvailableUnfilledIcon
   title: string
   action: () => void
+  hide?: boolean
 }
 
 type RailDropdown = {
@@ -20,6 +21,7 @@ type RailDropdown = {
   icon: AvailableUnfilledIcon
   title: string
   dropdown: ReactElement
+  hide?: boolean
 }
 
 export type RailAction = RailDropdown | RailActionButton
@@ -31,6 +33,10 @@ export default function RailActionElement({ action }: { action: RailAction }) {
     }
   }, [action])
 
+  if (action.hide) {
+    return null
+  }
+
   if ('dropdown' in action) {
     return (
       <Dropdown align="end" drop="end">
@@ -41,7 +47,7 @@ export default function RailActionElement({ action }: { action: RailAction }) {
         >
           <span>
             <DropdownToggle
-              id="rail-help-dropdown-btn"
+              id={`rail-dropdown-btn-${action.key}`}
               className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown"
               as="button"
               aria-label={action.title}

+ 32 - 0
services/web/frontend/js/features/ide-redesign/components/rail/rail-overflow-dropdown.tsx

@@ -0,0 +1,32 @@
+import { DropdownMenu } from '@/shared/components/dropdown/dropdown-menu'
+import { RailTabKey } from '../../contexts/rail-context'
+import { RailElement } from '../../utils/rail-types'
+import RailTab from './rail-tab'
+
+export default function RailOverflowDropdown({
+  tabs,
+  isOpen,
+  selectedTab,
+}: {
+  tabs: RailElement[]
+  isOpen: boolean
+  selectedTab: RailTabKey
+}) {
+  return (
+    <DropdownMenu className="ide-rail-overflow-dropdown">
+      {tabs
+        .filter(({ hide }) => !hide)
+        .map(({ icon, key, indicator, title, disabled }) => (
+          <RailTab
+            open={isOpen && selectedTab === key}
+            key={key}
+            eventKey={key}
+            icon={icon}
+            indicator={indicator}
+            title={title}
+            disabled={disabled}
+          />
+        ))}
+    </DropdownMenu>
+  )
+}

+ 38 - 15
services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx

@@ -29,6 +29,8 @@ import { RailElement } from '../../utils/rail-types'
 import RailPanel from './rail-panel'
 import RailResizeHandle from './rail-resize-handle'
 import RailModals from './rail-modals'
+import RailOverflowDropdown from './rail-overflow-dropdown'
+import useRailOverflow from '../../hooks/use-rail-overflow'
 
 export const RailLayout = () => {
   const { sendEvent } = useEditorAnalytics()
@@ -166,6 +168,25 @@ export const RailLayout = () => {
 
   const isReviewPanelOpen = selectedTab === 'review-panel'
 
+  const { tabsInRail, tabsInOverflow, tabWrapperRef } =
+    useRailOverflow(railTabs)
+
+  const moreOptionsAction: RailAction = useMemo(() => {
+    return {
+      key: 'more-options',
+      icon: 'more_vert',
+      title: t('more_options'),
+      hide: tabsInOverflow.length === 0,
+      dropdown: (
+        <RailOverflowDropdown
+          tabs={tabsInOverflow}
+          isOpen={isOpen}
+          selectedTab={selectedTab}
+        />
+      ),
+    }
+  }, [t, isOpen, selectedTab, tabsInOverflow])
+
   return (
     <TabContainer
       mountOnEnter // Only render when necessary (so that we can lazy load tab content)
@@ -183,22 +204,24 @@ export const RailLayout = () => {
         aria-label={t('files_collaboration_integrations_logs')}
       >
         <Nav activeKey={selectedTab} className="ide-rail-tabs-nav">
-          {railTabs
-            .filter(({ hide }) => !hide)
-            .map(({ icon, key, indicator, title, disabled }) => (
-              <RailTab
-                open={isOpen && selectedTab === key}
-                key={key}
-                eventKey={key}
-                icon={icon}
-                indicator={indicator}
-                title={title}
-                disabled={disabled}
-              />
-            ))}
-          <div className="flex-grow-1" />
+          <div className="ide-rail-tabs-wrapper" ref={tabWrapperRef}>
+            {tabsInRail
+              .filter(({ hide }) => !hide)
+              .map(({ icon, key, indicator, title, disabled }) => (
+                <RailTab
+                  open={isOpen && selectedTab === key}
+                  key={key}
+                  eventKey={key}
+                  icon={icon}
+                  indicator={indicator}
+                  title={title}
+                  disabled={disabled}
+                />
+              ))}
+            <RailActionElement key="more-options" action={moreOptionsAction} />
+          </div>
           <nav aria-label={t('help_editor_settings')}>
-            {railActions?.map(action => (
+            {railActions.map(action => (
               <RailActionElement key={action.key} action={action} />
             ))}
           </nav>

+ 35 - 0
services/web/frontend/js/features/ide-redesign/hooks/use-rail-overflow.tsx

@@ -0,0 +1,35 @@
+import { useCallback, useState } from 'react'
+import { RailElement } from '../utils/rail-types'
+import { useResizeObserver } from '@/shared/hooks/use-resize-observer'
+
+const useRailOverflow = (railTabs: RailElement[]) => {
+  const [tabsInRail, setTabsInRail] = useState<RailElement[]>(railTabs)
+  const [tabsInOverflow, setTabsInOverflow] = useState<RailElement[]>([])
+
+  const handleResize = useCallback(
+    (element: Element) => {
+      const height = (element as HTMLElement).offsetHeight
+
+      const tabHeight =
+        (element.querySelector('.ide-rail-tab-link')?.clientHeight ?? 0) + 4 // 4px gap between tabs
+
+      const numTabsToFit = Math.floor(height / tabHeight)
+
+      if (numTabsToFit >= railTabs.length) {
+        setTabsInRail(railTabs)
+        setTabsInOverflow([])
+      } else {
+        const sliceIndex = Math.max(numTabsToFit - 1, 0)
+        setTabsInRail(railTabs.slice(0, sliceIndex))
+        setTabsInOverflow(railTabs.slice(sliceIndex))
+      }
+    },
+    [railTabs]
+  )
+
+  const { elementRef: tabWrapperRef } = useResizeObserver(handleResize)
+
+  return { tabsInRail, tabsInOverflow, tabWrapperRef }
+}
+
+export default useRailOverflow

+ 19 - 1
services/web/frontend/stylesheets/pages/editor/rail.scss

@@ -148,11 +148,18 @@ body {
 .ide-rail-tabs-nav {
   height: 100%;
   display: flex;
-  flex-direction: column;
+  flex-flow: column nowrap;
   gap: var(--spacing-02);
   padding-bottom: var(--spacing-04);
 }
 
+.ide-rail-tabs-wrapper {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-02);
+}
+
 .ide-rail-tab-dropdown {
   border: 0;
 
@@ -161,6 +168,17 @@ body {
   }
 }
 
+.ide-rail-overflow-dropdown {
+  min-width: 0;
+  background-color: var(--ide-rail-background);
+
+  &.show {
+    display: flex;
+    flex-direction: column;
+    gap: var(--spacing-02);
+  }
+}
+
 .new-error-logs-promo {
   display: flex;
   gap: var(--spacing-04);