Эх сурвалжийг харах

Merge pull request #33932 from overleaf/mg-select-style

Replace text label with icon in "Select style" toolbar button

GitOrigin-RevId: 52b93a29db47e99609a90294e53abe1057a6c71d
Malik Glossop 2 сар өмнө
parent
commit
a47f6443f8

+ 1 - 2
services/web/frontend/extracted-translations.json

@@ -2113,7 +2113,6 @@
   "toolbar_bold": "",
   "toolbar_bulleted_list": "",
   "toolbar_change_editor_mode": "",
-  "toolbar_choose_section_heading_level": "",
   "toolbar_code_visual_editor_switch": "",
   "toolbar_decrease_indent": "",
   "toolbar_editor": "",
@@ -2138,7 +2137,7 @@
   "toolbar_numbered_list": "",
   "toolbar_redo": "",
   "toolbar_search_file": "",
-  "toolbar_select_style": "",
+  "toolbar_section_heading_level": "",
   "toolbar_selected_projects": "",
   "toolbar_selected_projects_management_actions": "",
   "toolbar_selected_projects_remove": "",

+ 18 - 1
services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx

@@ -15,6 +15,7 @@ export const ToolbarButtonMenu: FC<
     label: string
     icon: React.ReactNode
     orientation?: 'vertical' | 'horizontal'
+    className?: string
     disabled?: boolean
     disablePopover?: boolean
     altCommand?: (view: EditorView) => void
@@ -25,6 +26,7 @@ export const ToolbarButtonMenu: FC<
   id,
   label,
   orientation = 'vertical',
+  className,
   altCommand,
   onToggle,
   disabled,
@@ -35,6 +37,18 @@ export const ToolbarButtonMenu: FC<
   const { open, onToggle: handleToggle, ref } = useDropdown()
   const view = useCodeMirrorViewContext()
 
+  useEffect(() => {
+    if (!open) return
+
+    const onResize = () => {
+      handleToggle(false)
+    }
+    window.addEventListener('resize', onResize)
+    return () => {
+      window.removeEventListener('resize', onResize)
+    }
+  }, [open, handleToggle])
+
   useEffect(() => {
     if (disablePopover && open) {
       handleToggle(false)
@@ -48,9 +62,12 @@ export const ToolbarButtonMenu: FC<
   const button = (
     <button
       type="button"
-      className="ol-cm-toolbar-button"
+      className={classNames('ol-cm-toolbar-button', className)}
       aria-label={label}
       aria-disabled={disabled}
+      aria-haspopup="menu"
+      aria-expanded={open}
+      aria-controls={`${id}-menu`}
       onMouseDown={event => {
         event.preventDefault()
         event.stopPropagation()

+ 30 - 100
services/web/frontend/js/features/source-editor/components/toolbar/section-heading-dropdown.tsx

@@ -1,4 +1,3 @@
-import classnames from 'classnames'
 import {
   useCodeMirrorStateContext,
   useCodeMirrorViewContext,
@@ -7,15 +6,12 @@ import {
   findCurrentSectionHeadingLevel,
   setSectionHeadingLevel,
 } from '../../extensions/toolbar/sections'
-import { useCallback, useMemo, useRef } from 'react'
-import OLOverlay from '@/shared/components/ol/ol-overlay'
-import OLPopover from '@/shared/components/ol/ol-popover'
-import useEventListener from '../../../../shared/hooks/use-event-listener'
-import useDropdown from '../../../../shared/hooks/use-dropdown'
+import { useMemo } from 'react'
 import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
 import MaterialIcon from '@/shared/components/material-icon'
 import { useTranslation } from 'react-i18next'
-import OLTooltip from '@/shared/components/ol/ol-tooltip'
+import { ToolbarButtonMenu } from './button-menu'
+import OLListGroupItem from '@/shared/components/ol/ol-list-group-item'
 
 const levels = new Map([
   ['text', 'Normal text'],
@@ -33,107 +29,41 @@ export const SectionHeadingDropdown = () => {
   const view = useCodeMirrorViewContext()
   const { t } = useTranslation()
 
-  const { open: overflowOpen, onToggle: setOverflowOpen } = useDropdown()
-
-  useEventListener(
-    'resize',
-    useCallback(() => {
-      setOverflowOpen(false)
-    }, [setOverflowOpen])
-  )
-
-  const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
-
   const currentLevel = useMemo(
     () => findCurrentSectionHeadingLevel(state),
     [state]
   )
 
-  const currentLabel = currentLevel
-    ? (levels.get(currentLevel.level) ?? currentLevel.level)
-    : '---'
-
   return (
-    <>
-      <OLTooltip
-        id="section-heading-dropdown-tooltip"
-        description={t('toolbar_select_style')}
-        overlayProps={{ placement: 'bottom' }}
-      >
-        <button
-          ref={toggleButtonRef}
-          type="button"
-          id="section-heading-menu-button"
-          aria-haspopup="true"
-          aria-controls="section-heading-menu"
-          aria-label={t('toolbar_choose_section_heading_level')}
-          className="ol-cm-toolbar-menu-toggle"
-          onMouseDown={event => event.preventDefault()}
-          onClick={() => setOverflowOpen(!overflowOpen)}
-        >
-          <span>{currentLabel}</span>
+    <ToolbarButtonMenu
+      id="section-heading-menu-button"
+      label={t('toolbar_section_heading_level')}
+      className="ol-cm-toolbar-button-wide"
+      icon={
+        <>
+          <MaterialIcon
+            type="text_fields"
+            style={{ transform: 'scaleX(-1)' }}
+          />
           <MaterialIcon type="expand_more" />
-        </button>
-      </OLTooltip>
-
-      {overflowOpen && (
-        <OLOverlay
-          show
-          onHide={() => setOverflowOpen(false)}
-          transition={false}
-          container={view.dom}
-          containerPadding={0}
-          placement="bottom"
-          rootClose
-          target={toggleButtonRef.current}
-          popperConfig={{
-            modifiers: [
-              {
-                name: 'offset',
-                options: {
-                  offset: [0, 1],
-                },
-              },
-            ],
+        </>
+      }
+    >
+      {levelsEntries.map(([level, label]) => (
+        <OLListGroupItem
+          role="menuitem"
+          key={level}
+          active={level === currentLevel?.level}
+          onClick={() => {
+            emitToolbarEvent(view, 'section-level-change')
+            setSectionHeadingLevel(view, level)
+            view.focus()
           }}
+          className={`ol-cm-section-heading-menu-item section-level-${level}`}
         >
-          <OLPopover
-            id="popover-toolbar-section-heading"
-            className="ol-cm-toolbar-menu-popover"
-          >
-            <div
-              className="ol-cm-toolbar-menu"
-              id="section-heading-menu"
-              role="menu"
-              aria-labelledby="section-heading-menu-button"
-            >
-              {levelsEntries.map(([level, label]) => (
-                <button
-                  type="button"
-                  role="menuitem"
-                  key={level}
-                  onClick={() => {
-                    emitToolbarEvent(view, 'section-level-change')
-                    setSectionHeadingLevel(view, level)
-                    view.focus()
-                    setOverflowOpen(false)
-                  }}
-                  className={classnames(
-                    'ol-cm-toolbar-menu-item',
-                    `section-level-${level}`,
-                    {
-                      'ol-cm-toolbar-menu-item-active':
-                        level === currentLevel?.level,
-                    }
-                  )}
-                >
-                  {label}
-                </button>
-              ))}
-            </div>
-          </OLPopover>
-        </OLOverlay>
-      )}
-    </>
+          {label}
+        </OLListGroupItem>
+      ))}
+    </ToolbarButtonMenu>
   )
 }

+ 24 - 78
services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts

@@ -102,8 +102,29 @@ const toolbarTheme = EditorView.theme({
       color: 'var(--toolbar-btn-color)',
       borderColor: 'var(--editor-toolbar-bg)',
       background: 'none',
+      '&.active': {
+        backgroundColor: 'rgba(125, 125, 125, 0.1)',
+        color: 'inherit',
+      },
       '&:hover, &:focus': {
         backgroundColor: 'rgba(125, 125, 125, 0.2)',
+        color: 'inherit',
+      },
+      '&.ol-cm-section-heading-menu-item': {
+        border: 'none',
+        padding: '4px 12px',
+        height: '40px',
+        fontSize: '14px',
+        fontWeight: 'bold',
+      },
+      '&.section-level-section': {
+        fontSize: '1.44em',
+      },
+      '&.section-level-subsection': {
+        fontSize: '1.2em',
+      },
+      '&.section-level-text': {
+        fontWeight: 'normal',
       },
     },
   },
@@ -174,6 +195,9 @@ const toolbarTheme = EditorView.theme({
       fontWeight: 700,
     },
   },
+  '.ol-cm-toolbar-button.ol-cm-toolbar-button-wide': {
+    width: 'auto',
+  },
   '&.overall-theme-dark .ol-cm-toolbar-button': {
     opacity: 0.8,
     '&:hover, &:focus, &:active, &.active': {
@@ -198,84 +222,6 @@ const toolbarTheme = EditorView.theme({
       display: 'flex',
     },
   },
-  '.ol-cm-toolbar-menu-toggle': {
-    background: 'transparent',
-    border: 'none',
-    color: 'inherit',
-    borderRadius: 'var(--border-radius-base)',
-    opacity: 0.8,
-    width: '120px',
-    fontSize: '13px',
-    fontWeight: '700',
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'space-between',
-    padding: '5px 6px',
-    '&:hover, &:focus, &.active': {
-      backgroundColor: 'rgba(125, 125, 125, 0.1)',
-      opacity: '1',
-      color: 'inherit',
-    },
-    '& .caret': {
-      marginTop: '0',
-    },
-  },
-  '.ol-cm-toolbar-menu-popover': {
-    border: 'none',
-    borderRadius: '0',
-    borderBottomLeftRadius: '4px',
-    borderBottomRightRadius: '4px',
-    boxShadow: '0 2px 5px rgb(0 0 0 / 20%)',
-    backgroundColor: 'var(--editor-toolbar-bg)',
-    color: 'var(--toolbar-btn-color)',
-    padding: '0',
-    '&.bottom': {
-      marginTop: '1px',
-    },
-    '&.top': {
-      marginBottom: '1px',
-    },
-    '& .arrow, & .popover-arrow': {
-      display: 'none',
-    },
-    '& .popover-content, & > .popover-body': {
-      padding: '0',
-      color: 'inherit',
-    },
-    '& .ol-cm-toolbar-menu': {
-      minWidth: '120px',
-      display: 'flex',
-      flexDirection: 'column',
-      boxSizing: 'border-box',
-      fontSize: '14px',
-    },
-    '& .ol-cm-toolbar-menu-item': {
-      border: 'none',
-      background: 'none',
-      padding: '4px 12px',
-      height: '40px',
-      display: 'flex',
-      alignItems: 'center',
-      fontWeight: 'bold',
-      color: 'inherit',
-      '&.ol-cm-toolbar-menu-item-active': {
-        backgroundColor: 'rgba(125, 125, 125, 0.1)',
-      },
-      '&:hover': {
-        backgroundColor: 'rgba(125, 125, 125, 0.2)',
-        color: 'inherit',
-      },
-      '&.section-level-section': {
-        fontSize: '1.44em',
-      },
-      '&.section-level-subsection': {
-        fontSize: '1.2em',
-      },
-      '&.section-level-body': {
-        fontWeight: 'normal',
-      },
-    },
-  },
   '&.overall-theme-dark .ol-cm-toolbar-table-grid': {
     '& td.active': {
       outlineColor: 'white',

+ 1 - 2
services/web/locales/en.json

@@ -2727,7 +2727,6 @@
   "toolbar_bold": "Bold",
   "toolbar_bulleted_list": "Bulleted list",
   "toolbar_change_editor_mode": "Change editor mode: Code / Visual",
-  "toolbar_choose_section_heading_level": "Choose section heading level",
   "toolbar_code_visual_editor_switch": "Code and visual editor switch",
   "toolbar_decrease_indent": "Decrease indent",
   "toolbar_editor": "Editor tools",
@@ -2752,7 +2751,7 @@
   "toolbar_numbered_list": "Numbered list",
   "toolbar_redo": "Redo",
   "toolbar_search_file": "Search file",
-  "toolbar_select_style": "Select style",
+  "toolbar_section_heading_level": "Section heading level",
   "toolbar_selected_projects": "Selected projects",
   "toolbar_selected_projects_management_actions": "Selected projects management actions",
   "toolbar_selected_projects_remove": "Remove selected projects",

+ 2 - 2
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx

@@ -68,14 +68,14 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
     mountEditor('hi')
     cy.get('.cm-content').should('have.text', 'hi')
 
-    clickToolbarButton('Choose section heading level')
+    clickToolbarButton('Section heading level')
     cy.findByRole('menu').within(() => {
       cy.findByText('Subsection').click()
     })
     cy.get('.cm-content').should('have.text', 'hi')
     cy.get('.ol-cm-command-subsection').should('have.length', 1)
 
-    clickToolbarButton('Choose section heading level')
+    clickToolbarButton('Section heading level')
     cy.findByRole('menu').within(() => {
       cy.findByText('Normal text').click()
     })