mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #23300 from overleaf/mj-ide-menu-hover
[web] Introduce menu bar shared component GitOrigin-RevId: c304cc4e1e5961fe4ef7d2112e8d9f91c47dd0ec
This commit is contained in:
@@ -1,32 +1,29 @@
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownDivider,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
|
||||
import { FC } from 'react'
|
||||
import { DropdownDivider } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import { MenuBar } from '@/shared/components/menu-bar/menu-bar'
|
||||
import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
|
||||
import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type MenuBarOptionProps = {
|
||||
title: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
type MenuBarDropdownProps = {
|
||||
title: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export const ToolbarMenuBar = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="ide-redesign-toolbar-menu-bar">
|
||||
<MenuBarDropdown title={t('file')} id="file">
|
||||
<MenuBar
|
||||
className="ide-redesign-toolbar-menu-bar"
|
||||
id="toolbar-menu-bar-item"
|
||||
>
|
||||
<MenuBarDropdown
|
||||
title={t('file')}
|
||||
id="file"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
<MenuBarOption title="New File" />
|
||||
<MenuBarOption title="New Project" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title={t('edit')} id="edit">
|
||||
<MenuBarDropdown
|
||||
title={t('edit')}
|
||||
id="edit"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
<MenuBarOption title="Undo" />
|
||||
<MenuBarOption title="Redo" />
|
||||
<DropdownDivider />
|
||||
@@ -34,44 +31,41 @@ export const ToolbarMenuBar = () => {
|
||||
<MenuBarOption title="Copy" />
|
||||
<MenuBarOption title="Pate" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title={t('view')} id="view">
|
||||
<MenuBarDropdown
|
||||
title={t('view')}
|
||||
id="view"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
<MenuBarOption title="PDF only" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title={t('insert')} id="insert">
|
||||
<MenuBarDropdown
|
||||
title={t('insert')}
|
||||
id="insert"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
<MenuBarOption title="Insert figure" />
|
||||
<MenuBarOption title="Insert table" />
|
||||
<MenuBarOption title="Insert link" />
|
||||
<MenuBarOption title="Add comment" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title={t('format')} id="format">
|
||||
<MenuBarDropdown
|
||||
title={t('format')}
|
||||
id="format"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
<MenuBarOption title="Bold text" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title={t('help')} id="help">
|
||||
<MenuBarDropdown
|
||||
title={t('help')}
|
||||
id="help"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
<MenuBarOption title="Keyboard shortcuts" />
|
||||
<MenuBarOption title="Documentation" />
|
||||
<DropdownDivider />
|
||||
<MenuBarOption title="Contact us" />
|
||||
<MenuBarOption title="Give feedback" />
|
||||
</MenuBarDropdown>
|
||||
</div>
|
||||
</MenuBar>
|
||||
)
|
||||
}
|
||||
|
||||
const MenuBarDropdown: FC<MenuBarDropdownProps> = ({ title, children, id }) => {
|
||||
return (
|
||||
<Dropdown align="start">
|
||||
<DropdownToggle
|
||||
id={`toolbar-menu-bar-item-${id}`}
|
||||
variant="secondary"
|
||||
className="ide-redesign-toolbar-dropdown-toggle-subdued"
|
||||
>
|
||||
{title}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>{children}</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
const MenuBarOption = ({ title, onClick }: MenuBarOptionProps) => {
|
||||
return <OLDropdownMenuItem onClick={onClick}>{title}</OLDropdownMenuItem>
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export type DropdownToggleProps = PropsWithChildren<{
|
||||
size?: 'sm' | 'lg' | undefined
|
||||
tabIndex?: number
|
||||
'aria-label'?: string
|
||||
onMouseEnter?: React.MouseEventHandler
|
||||
}>
|
||||
|
||||
export type DropdownMenuProps = PropsWithChildren<{
|
||||
@@ -66,6 +67,7 @@ export type DropdownMenuProps = PropsWithChildren<{
|
||||
className?: string
|
||||
flip?: boolean
|
||||
id?: string
|
||||
renderOnMount?: boolean
|
||||
}>
|
||||
|
||||
export type DropdownDividerProps = PropsWithChildren<{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import { FC, useCallback } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useMenuBar } from '@/shared/hooks/use-menu-bar'
|
||||
|
||||
type MenuBarDropdownProps = {
|
||||
title: string
|
||||
id: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const MenuBarDropdown: FC<MenuBarDropdownProps> = ({
|
||||
title,
|
||||
children,
|
||||
id,
|
||||
className,
|
||||
}) => {
|
||||
const { menuId, selected, setSelected } = useMenuBar()
|
||||
|
||||
const onToggle = useCallback(
|
||||
show => {
|
||||
setSelected(show ? id : null)
|
||||
},
|
||||
[id, setSelected]
|
||||
)
|
||||
|
||||
const onHover = useCallback(() => {
|
||||
setSelected(prev => {
|
||||
if (prev === null) {
|
||||
return null
|
||||
}
|
||||
return id
|
||||
})
|
||||
}, [id, setSelected])
|
||||
|
||||
return (
|
||||
<Dropdown show={selected === id} align="start" onToggle={onToggle}>
|
||||
<DropdownToggle
|
||||
id={`${menuId}-${id}`}
|
||||
variant="secondary"
|
||||
className={classNames(className, 'menu-bar-toggle')}
|
||||
onMouseEnter={onHover}
|
||||
>
|
||||
{title}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu renderOnMount>{children}</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item'
|
||||
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
|
||||
type MenuBarOptionProps = {
|
||||
title: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export const MenuBarOption = ({ title, onClick }: MenuBarOptionProps) => {
|
||||
return (
|
||||
<DropdownListItem>
|
||||
<DropdownItem onClick={onClick}>{title}</DropdownItem>
|
||||
</DropdownListItem>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MenuBarContext } from '@/shared/context/menu-bar-context'
|
||||
import { FC, HTMLProps, useState } from 'react'
|
||||
|
||||
export const MenuBar: FC<HTMLProps<HTMLDivElement> & { id: string }> = ({
|
||||
children,
|
||||
id,
|
||||
...props
|
||||
}) => {
|
||||
const [selected, setSelected] = useState<string | null>(null)
|
||||
return (
|
||||
<div {...props}>
|
||||
<MenuBarContext.Provider value={{ selected, setSelected, menuId: id }}>
|
||||
{children}
|
||||
</MenuBarContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
11
services/web/frontend/js/shared/context/menu-bar-context.tsx
Normal file
11
services/web/frontend/js/shared/context/menu-bar-context.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createContext, Dispatch, SetStateAction } from 'react'
|
||||
|
||||
export type MenuBarContextType = {
|
||||
selected: string | null
|
||||
setSelected: Dispatch<SetStateAction<string | null>>
|
||||
menuId: string
|
||||
}
|
||||
|
||||
export const MenuBarContext = createContext<MenuBarContextType | undefined>(
|
||||
undefined
|
||||
)
|
||||
10
services/web/frontend/js/shared/hooks/use-menu-bar.tsx
Normal file
10
services/web/frontend/js/shared/hooks/use-menu-bar.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { MenuBarContext } from '@/shared/context/menu-bar-context'
|
||||
import { useContext } from 'react'
|
||||
|
||||
export const useMenuBar = () => {
|
||||
const context = useContext(MenuBarContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useMenuBarContext must be used within a MenuBarContext')
|
||||
}
|
||||
return context
|
||||
}
|
||||
38
services/web/frontend/stories/menu-bar.stories.tsx
Normal file
38
services/web/frontend/stories/menu-bar.stories.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DropdownDivider } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import { MenuBar } from '@/shared/components/menu-bar/menu-bar'
|
||||
import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
|
||||
import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
|
||||
import { Meta } from '@storybook/react/*'
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<MenuBar id="toolbar-menu-bar-item">
|
||||
<MenuBarDropdown title="File" id="file">
|
||||
<MenuBarOption title="New File" />
|
||||
<MenuBarOption title="New Project" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title="Edit" id="edit">
|
||||
<MenuBarOption title="Undo" />
|
||||
<MenuBarOption title="Redo" />
|
||||
<DropdownDivider />
|
||||
<MenuBarOption title="Cut" />
|
||||
<MenuBarOption title="Copy" />
|
||||
<MenuBarOption title="Paste" />
|
||||
</MenuBarDropdown>
|
||||
<MenuBarDropdown title="View" id="view">
|
||||
<MenuBarOption title="PDF only" />
|
||||
</MenuBarDropdown>
|
||||
</MenuBar>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta<typeof MenuBar> = {
|
||||
title: 'Shared / Components / MenuBar',
|
||||
component: MenuBar,
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
bootstrap5: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -37,3 +37,4 @@
|
||||
@import 'tos';
|
||||
@import 'collapsible-file-header';
|
||||
@import 'panel-heading';
|
||||
@import 'menu-bar';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
.menu-bar-toggle {
|
||||
border: none;
|
||||
border-radius: var(--border-radius-base);
|
||||
padding: var(--spacing-02);
|
||||
font-size: var(--font-size-03);
|
||||
line-height: var(--line-height-03);
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user