Merge pull request #20740 from overleaf/rd-ide-offcanvas

[web] Implement the editor's left menu in Offcanvas

GitOrigin-RevId: 999e995d664b1dc958f56643f05e95b8aa2d6290
This commit is contained in:
Rebeka Dekany 2024-10-11 11:22:38 +02:00 committed by Copybot
parent 7a26d46d7c
commit f8efc3e2ae
40 changed files with 865 additions and 239 deletions

View file

@ -29,4 +29,12 @@
direction: ltr;
font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
&.size-2x {
font-size: 2em;
}
&.rotate-180 {
transform: rotate(180deg);
}
}

View file

@ -1,12 +1,20 @@
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Button, Modal } from 'react-bootstrap'
import Icon from '../../../shared/components/icon'
import Tooltip from '../../../shared/components/tooltip'
import useAsync from '../../../shared/hooks/use-async'
import { postJSON } from '../../../infrastructure/fetch-json'
import ignoredWords from '../ignored-words'
import { debugConsole } from '@/utils/debugging'
import {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
import { bsVersion } from '@/features/utils/bootstrap-5'
type DictionaryModalContentProps = {
handleHide: () => void
@ -42,13 +50,16 @@ export default function DictionaryModalContent({
return (
<>
<Modal.Header closeButton>
<Modal.Title>{t('edit_dictionary')}</Modal.Title>
</Modal.Header>
<OLModalHeader closeButton>
<OLModalTitle>{t('edit_dictionary')}</OLModalTitle>
</OLModalHeader>
<Modal.Body>
<OLModalBody>
{isError ? (
<Alert bsStyle="danger">{t('generic_something_went_wrong')}</Alert>
<OLNotification
type="error"
content={t('generic_something_went_wrong')}
/>
) : null}
{learnedWords?.size > 0 ? (
@ -56,22 +67,25 @@ export default function DictionaryModalContent({
{[...learnedWords].sort(wordsSortFunction).map(learnedWord => (
<li key={learnedWord} className="dictionary-entry">
<span className="dictionary-entry-name">{learnedWord}</span>
<Tooltip
<OLTooltip
id={`tooltip-remove-learned-word-${learnedWord}`}
description={t('edit_dictionary_remove')}
overlayProps={{ delay: 0 }}
>
<Button
bsStyle="danger"
bsSize="xs"
<OLIconButton
variant="danger"
size="sm"
onClick={() => handleRemove(learnedWord)}
>
<Icon
type="trash-o"
bs3Props={{ bsSize: 'xsmall' }}
icon={
bsVersion({
bs5: 'delete',
bs3: 'trash-o',
}) as string
}
accessibilityLabel={t('edit_dictionary_remove')}
/>
</Button>
</Tooltip>
</OLTooltip>
</li>
))}
</ul>
@ -80,13 +94,13 @@ export default function DictionaryModalContent({
<i>{t('edit_dictionary_empty')}</i>
</p>
)}
</Modal.Body>
</OLModalBody>
<Modal.Footer>
<Button bsStyle={null} className="btn-secondary" onClick={handleHide}>
<OLModalFooter>
<OLButton variant="secondary" onClick={handleHide}>
{t('close')}
</Button>
</Modal.Footer>
</OLButton>
</OLModalFooter>
</>
)
}

View file

@ -1,7 +1,7 @@
import React from 'react'
import DictionaryModalContent from './dictionary-modal-content'
import AccessibleModal from '../../../shared/components/accessible-modal'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import OLModal from '@/features/ui/components/ol/ol-modal'
type DictionaryModalProps = {
show?: boolean
@ -10,15 +10,15 @@ type DictionaryModalProps = {
function DictionaryModal({ show, handleHide }: DictionaryModalProps) {
return (
<AccessibleModal
<OLModal
animation
show={show}
onHide={handleHide}
id="dictionary-modal"
bsSize="small"
size="sm"
>
<DictionaryModalContent handleHide={handleHide} />
</AccessibleModal>
</OLModal>
)
}

View file

@ -4,6 +4,7 @@ import EditorCloneProjectModalWrapper from '../../clone-project-modal/components
import LeftMenuButton from './left-menu-button'
import { useLocation } from '../../../shared/hooks/use-location'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
type ProjectCopyResponse = {
project_id: string
@ -30,10 +31,10 @@ export default function ActionsCopyProject() {
<>
<LeftMenuButton
onClick={handleShowModal}
icon={{
type: 'copy',
fw: true,
}}
icon={bsVersionIcon({
bs5: { type: 'file_copy' },
bs3: { type: 'copy', fw: true },
})}
>
{t('copy_project')}
</LeftMenuButton>

View file

@ -1,10 +1,11 @@
import { useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '../../../shared/components/tooltip'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import WordCountModal from '../../word-count-modal/components/word-count-modal'
import LeftMenuButton from './left-menu-button'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
export default function ActionsWordCount() {
const [showModal, setShowModal] = useState(false)
@ -21,15 +22,15 @@ export default function ActionsWordCount() {
{pdfUrl ? (
<LeftMenuButton
onClick={handleShowModal}
icon={{
type: 'eye',
fw: true,
}}
icon={bsVersionIcon({
bs5: { type: 'match_case' },
bs3: { type: 'eye', fw: true },
})}
>
{t('word_count')}
</LeftMenuButton>
) : (
<Tooltip
<OLTooltip
id="disabled-word-count"
description={t('please_compile_pdf_before_word_count')}
overlayProps={{
@ -39,10 +40,10 @@ export default function ActionsWordCount() {
{/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
<div>
<LeftMenuButton
icon={{
type: 'eye',
fw: true,
}}
icon={bsVersionIcon({
bs5: { type: 'match_case' },
bs3: { type: 'eye', fw: true },
})}
disabled
disabledAccesibilityText={t(
'please_compile_pdf_before_word_count'
@ -51,7 +52,7 @@ export default function ActionsWordCount() {
{t('word_count')}
</LeftMenuButton>
</div>
</Tooltip>
</OLTooltip>
)}
<WordCountModal show={showModal} handleHide={() => setShowModal(false)} />
</>

View file

@ -2,9 +2,11 @@ import { useTranslation } from 'react-i18next'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useProjectContext } from '../../../shared/context/project-context'
import Icon from '../../../shared/components/icon'
import Tooltip from '../../../shared/components/tooltip'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { isSmallDevice } from '../../../infrastructure/event-tracking'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import MaterialIcon from '@/shared/components/material-icon'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
export default function DownloadPDF() {
const { t } = useTranslation()
@ -27,24 +29,30 @@ export default function DownloadPDF() {
rel="noreferrer"
onClick={sendDownloadEvent}
>
<Icon type="file-pdf-o" modifier="2x" />
<BootstrapVersionSwitcher
bs3={<Icon type="file-pdf-o" modifier="2x" />}
bs5={<MaterialIcon type="picture_as_pdf" size="2x" />}
/>
<br />
PDF
</a>
)
} else {
return (
<Tooltip
<OLTooltip
id="disabled-pdf-download"
description={t('please_compile_pdf_before_download')}
overlayProps={{ placement: 'bottom' }}
>
<div className="link-disabled">
<Icon type="file-pdf-o" modifier="2x" />
<BootstrapVersionSwitcher
bs3={<Icon type="file-pdf-o" modifier="2x" />}
bs5={<MaterialIcon type="picture_as_pdf" size="2x" />}
/>
<br />
PDF
</div>
</Tooltip>
</OLTooltip>
)
}
}

View file

@ -3,6 +3,8 @@ import { useProjectContext } from '../../../shared/context/project-context'
import Icon from '../../../shared/components/icon'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { isSmallDevice } from '../../../infrastructure/event-tracking'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import MaterialIcon from '@/shared/components/material-icon'
export default function DownloadSource() {
const { t } = useTranslation()
@ -23,7 +25,10 @@ export default function DownloadSource() {
rel="noreferrer"
onClick={sendDownloadEvent}
>
<Icon type="file-archive-o" modifier="2x" />
<BootstrapVersionSwitcher
bs3={<Icon type="file-archive-o" modifier="2x" />}
bs5={<MaterialIcon type="folder_zip" size="2x" />}
/>
<br />
{t('source')}
</a>

View file

@ -5,21 +5,25 @@ import { Modal } from 'react-bootstrap'
import classNames from 'classnames'
import { lazy, memo, Suspense } from 'react'
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { Offcanvas } from 'react-bootstrap-5'
const EditorLeftMenuBody = lazy(() => import('./editor-left-menu-body'))
function EditorLeftMenu() {
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
const closeModal = () => {
const closeLeftMenu = () => {
setLeftMenuShown(false)
}
return (
<BootstrapVersionSwitcher
bs3={
<>
<AccessibleModal
backdropClassName="left-menu-modal-backdrop"
keyboard
onHide={closeModal}
onHide={closeLeftMenu}
id="left-menu-modal"
show={leftMenuShown}
>
@ -34,6 +38,30 @@ function EditorLeftMenu() {
</AccessibleModal>
{leftMenuShown && <LeftMenuMask />}
</>
}
bs5={
<>
<Offcanvas
show={leftMenuShown}
onHide={closeLeftMenu}
backdropClassName="left-menu-modal-backdrop"
id="left-menu-offcanvas"
>
<Offcanvas.Body
className={classNames('full-size', 'left-menu', {
shown: leftMenuShown,
})}
id="left-menu"
>
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
<EditorLeftMenuBody />
</Suspense>
</Offcanvas.Body>
</Offcanvas>
{leftMenuShown && <LeftMenuMask />}
</>
}
/>
)
}

View file

@ -3,6 +3,7 @@ import { useCallback } from 'react'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { useContactUsModal } from '../../../shared/hooks/use-contact-us-modal'
import LeftMenuButton from './left-menu-button'
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
export default function HelpContactUs() {
const { modal, showModal } = useContactUsModal()
@ -17,10 +18,10 @@ export default function HelpContactUs() {
<>
<LeftMenuButton
onClick={showModalWithAnalytics}
icon={{
type: 'question',
fw: true,
}}
icon={bsVersionIcon({
bs5: { type: 'contact_support' },
bs3: { type: 'question', fw: true },
})}
>
{t('contact_us')}
</LeftMenuButton>

View file

@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next'
import LeftMenuButton from './left-menu-button'
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
export default function HelpDocumentation() {
const { t } = useTranslation()
@ -9,10 +10,10 @@ export default function HelpDocumentation() {
<LeftMenuButton
type="link"
href="/learn"
icon={{
type: 'book',
fw: true,
}}
icon={bsVersionIcon({
bs5: { type: 'book_4' },
bs3: { type: 'book', fw: true },
})}
>
{t('documentation')}
</LeftMenuButton>

View file

@ -4,6 +4,7 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
import { useProjectContext } from '../../../shared/context/project-context'
import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
import LeftMenuButton from './left-menu-button'
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
export default function HelpShowHotkeys() {
const [showModal, setShowModal] = useState(false)
@ -20,10 +21,10 @@ export default function HelpShowHotkeys() {
<>
<LeftMenuButton
onClick={showModalWithAnalytics}
icon={{
type: 'keyboard-o',
fw: true,
}}
icon={bsVersionIcon({
bs5: { type: 'keyboard' },
bs3: { type: 'keyboard-o', fw: true },
})}
>
{t('show_hotkeys')}
</LeftMenuButton>

View file

@ -1,20 +1,43 @@
import { PropsWithChildren } from 'react'
import Icon from '../../../shared/components/icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import MaterialIcon from '@/shared/components/material-icon'
type Props = {
onClick?: () => void
icon: {
icon?: {
type: string
fw?: boolean
}
svgIcon?: React.ReactElement | null
disabled?: boolean
disabledAccesibilityText?: string
type?: 'button' | 'link'
href?: string
}
function LeftMenuButtonIcon({
svgIcon,
icon,
}: {
svgIcon?: React.ReactElement | null
icon?: { type: string; fw?: boolean }
}) {
if (svgIcon) {
return <div className="material-symbols">{svgIcon}</div>
} else if (icon) {
return (
<BootstrapVersionSwitcher
bs3={<Icon type={icon.type} fw={icon.fw ?? false} />}
bs5={<MaterialIcon type={icon.type} />}
/>
)
} else return null
}
export default function LeftMenuButton({
children,
svgIcon,
onClick,
icon,
disabled = false,
@ -25,7 +48,7 @@ export default function LeftMenuButton({
if (disabled) {
return (
<div className="left-menu-button link-disabled">
<Icon type={icon.type} fw={icon.fw} />
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
<span>{children}</span>
{disabledAccesibilityText ? (
<span className="sr-only">{disabledAccesibilityText}</span>
@ -37,7 +60,7 @@ export default function LeftMenuButton({
if (type === 'button') {
return (
<button onClick={onClick} className="left-menu-button">
<Icon type={icon.type} fw={icon.fw} />
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
<span>{children}</span>
</button>
)
@ -49,7 +72,7 @@ export default function LeftMenuButton({
rel="noreferrer"
className="left-menu-button"
>
<Icon type={icon.type} fw={icon.fw} />
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
<span>{children}</span>
</a>
)

View file

@ -1,4 +1,3 @@
import { Form } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../utils/meta'
import SettingsAutoCloseBrackets from './settings/settings-auto-close-brackets'
@ -20,6 +19,7 @@ import SettingsMathPreview from './settings/settings-math-preview'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import { ElementType } from 'react'
import OLForm from '@/features/ui/components/ol/ol-form'
const moduleSettings: Array<{
import: { default: ElementType }
@ -38,7 +38,7 @@ export default function SettingsMenu() {
return (
<>
<h4>{t('settings')}</h4>
<Form className="settings">
<OLForm id="left-menu-setting" className="settings">
<SettingsCompiler />
<SettingsImageName />
<SettingsDocument />
@ -58,7 +58,7 @@ export default function SettingsMenu() {
<SettingsFontFamily />
<SettingsLineHeight />
<SettingsPdfViewer />
</Form>
</OLForm>
</>
)
}

View file

@ -1,28 +1,31 @@
import { useState } from 'react'
import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import DictionaryModal from '../../../dictionary/components/dictionary-modal'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
export default function SettingsDictionary() {
const { t } = useTranslation()
const [showModal, setShowModal] = useState(false)
return (
<div className="form-group left-menu-setting">
<label htmlFor="dictionary">{t('dictionary')}</label>
<Button
className="btn-secondary"
bsSize="xs"
bsStyle={null}
<OLFormGroup className="left-menu-setting">
<OLFormLabel htmlFor="dictionary-settings">{t('dictionary')}</OLFormLabel>
<OLButton
id="dictionary-settings"
variant="secondary"
size="sm"
onClick={() => setShowModal(true)}
bs3Props={{ bsSize: 'xsmall' }}
>
{t('edit')}
</Button>
</OLButton>
<DictionaryModal
show={showModal}
handleHide={() => setShowModal(false)}
/>
</div>
</OLFormGroup>
)
}

View file

@ -1,4 +1,9 @@
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
import { ChangeEventHandler, useCallback } from 'react'
import { Spinner } from 'react-bootstrap-5'
type PossibleValue = string | number | boolean
@ -50,16 +55,32 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
)
return (
<div className="form-group left-menu-setting">
<label htmlFor={`settings-menu-${name}`}>{label}</label>
<OLFormGroup
controlId={`settings-menu-${name}`}
className="left-menu-setting"
>
<OLFormLabel>{label}</OLFormLabel>
{loading ? (
<BootstrapVersionSwitcher
bs3={
<p className="loading pull-right">
<i className="fa fa-fw fa-spin fa-refresh" />
</p>
}
bs5={
<p className="mb-0">
<Spinner
animation="border"
aria-hidden="true"
size="sm"
role="status"
/>
</p>
}
/>
) : (
<select
id={`settings-menu-${name}`}
className="form-control"
<OLFormSelect
size="sm"
onChange={handleChange}
value={value?.toString()}
disabled={disabled}
@ -86,8 +107,8 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
))}
</optgroup>
) : null}
</select>
</OLFormSelect>
)}
</div>
</OLFormGroup>
)
}

View file

@ -1,8 +1,15 @@
import { Button, Modal, Row, Col } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { Trans, useTranslation } from 'react-i18next'
import AccessibleModal from '../../../shared/components/accessible-modal'
import HotkeysModalBottomText from './hotkeys-modal-bottom-text'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLRow from '@/features/ui/components/ol/ol-row'
import OLCol from '@/features/ui/components/ol/ol-col'
export default function HotkeysModal({
animation = true,
@ -16,21 +23,16 @@ export default function HotkeysModal({
const ctrl = isMac ? 'Cmd' : 'Ctrl'
return (
<AccessibleModal
bsSize="large"
onHide={handleHide}
show={show}
animation={animation}
>
<Modal.Header closeButton>
<Modal.Title>{t('hotkeys')}</Modal.Title>
</Modal.Header>
<OLModal size="lg" onHide={handleHide} show={show} animation={animation}>
<OLModalHeader closeButton>
<OLModalTitle>{t('hotkeys')}</OLModalTitle>
</OLModalHeader>
<Modal.Body className="hotkeys-modal">
<OLModalBody className="hotkeys-modal">
<h3>{t('common')}</h3>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + F`}
description={t('hotkey_find_and_replace')}
@ -39,48 +41,48 @@ export default function HotkeysModal({
combination={`${ctrl} + Enter`}
description={t('hotkey_compile')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + Z`}
description={t('hotkey_undo')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + Y`}
description={t('hotkey_redo')}
/>
</Col>
</Row>
</OLCol>
</OLRow>
<h3>{t('navigation')}</h3>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + Home`}
description={t('hotkey_beginning_of_document')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + End`}
description={t('hotkey_end_of_document')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + Shift + L`}
description={t('hotkey_go_to_line')}
/>
</Col>
</Row>
</OLCol>
</OLRow>
<h3>{t('editing')}</h3>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + /`}
description={t('hotkey_toggle_comment')}
@ -93,9 +95,9 @@ export default function HotkeysModal({
combination={`${ctrl} + A`}
description={t('hotkey_select_all')}
/>
</Col>
</OLCol>
<Col xs={4}>
<OLCol xs={4}>
<Hotkey
combination="Ctrl + U"
description={t('hotkey_to_uppercase')}
@ -108,9 +110,9 @@ export default function HotkeysModal({
combination="Tab"
description={t('hotkey_indent_selection')}
/>
</Col>
</OLCol>
<Col xs={4}>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + B`}
description={t('hotkey_bold_text')}
@ -119,31 +121,31 @@ export default function HotkeysModal({
combination={`${ctrl} + I`}
description={t('hotkey_italic_text')}
/>
</Col>
</Row>
</OLCol>
</OLRow>
<h3>{t('autocomplete')}</h3>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<Hotkey
combination="Ctrl + Space"
description={t('hotkey_autocomplete_menu')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination="Up / Down"
description={t('hotkey_select_candidate')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination="Enter / Tab"
description={t('hotkey_insert_candidate')}
/>
</Col>
</Row>
</OLCol>
</OLRow>
<h3>
<Trans
@ -152,50 +154,50 @@ export default function HotkeysModal({
/>
</h3>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<Hotkey
combination={`Ctrl + Space `}
description={t('hotkey_search_references')}
/>
</Col>
</Row>
</OLCol>
</OLRow>
{trackChangesVisible && (
<>
<h3>{t('review')}</h3>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + J`}
description={t('hotkey_toggle_review_panel')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + Shift + A`}
description={t('hotkey_toggle_track_changes')}
/>
</Col>
<Col xs={4}>
</OLCol>
<OLCol xs={4}>
<Hotkey
combination={`${ctrl} + Shift + C`}
description={t('hotkey_add_a_comment')}
/>
</Col>
</Row>
</OLCol>
</OLRow>
</>
)}
<HotkeysModalBottomText />
</Modal.Body>
</OLModalBody>
<Modal.Footer>
<Button bsStyle={null} className="btn-secondary" onClick={handleHide}>
<OLModalFooter>
<OLButton variant="secondary" onClick={handleHide}>
{t('close')}
</Button>
</Modal.Footer>
</AccessibleModal>
</OLButton>
</OLModalFooter>
</OLModal>
)
}

View file

@ -35,6 +35,7 @@ export function bs3ButtonProps(props: ButtonProps) {
disabled: props.isLoading || props.disabled,
form: props.form,
href: props.href,
id: props.id,
target: props.target,
rel: props.rel,
onClick: props.onClick,

View file

@ -9,6 +9,7 @@ export type ButtonProps = {
form?: string
leadingIcon?: string | React.ReactNode
href?: string
id?: string
target?: string
rel?: string
isLoading?: boolean

View file

@ -12,6 +12,16 @@ export function bsVersion({ bs5, bs3 }: { bs5?: unknown; bs3?: unknown }) {
return isBootstrap5() ? bs5 : bs3
}
export const bsVersionIcon = ({
bs5,
bs3,
}: {
bs5?: { type: string }
bs3?: { type: string; fw?: boolean }
}) => {
return isBootstrap5() ? bs5 : bs3
}
// get all `aria-*` and `data-*` attributes
export const getAriaAndDataProps = (obj: Record<string, unknown>) => {
return Object.entries(obj).reduce(

View file

@ -1,10 +1,21 @@
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import { Alert, Button, Modal, Row, Col, Grid } from 'react-bootstrap'
import { useIdeContext } from '../../../shared/context/ide-context'
import { useProjectContext } from '../../../shared/context/project-context'
import { useWordCount } from '../hooks/use-word-count'
import Icon from '../../../shared/components/icon'
import {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import OLRow from '@/features/ui/components/ol/ol-row'
import OLCol from '@/features/ui/components/ol/ol-col'
import OLButton from '@/features/ui/components/ol/ol-button'
import Icon from '@/shared/components/icon'
import { Spinner } from 'react-bootstrap-5'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
// NOTE: this component is only mounted when the modal is open
export default function WordCountModalContent({ handleHide }) {
@ -15,71 +26,87 @@ export default function WordCountModalContent({ handleHide }) {
return (
<>
<Modal.Header closeButton>
<Modal.Title>{t('word_count')}</Modal.Title>
</Modal.Header>
<OLModalHeader closeButton>
<OLModalTitle>{t('word_count')}</OLModalTitle>
</OLModalHeader>
<Modal.Body>
<OLModalBody>
{loading && !error && (
<div className="loading">
<Icon type="refresh" spin fw />
<BootstrapVersionSwitcher
bs3={<Icon type="refresh" spin fw />}
bs5={
<Spinner
animation="border"
aria-hidden="true"
size="sm"
role="status"
/>
}
/>
&nbsp;
{t('loading')}
</div>
)}
{error && (
<Alert bsStyle="danger">{t('generic_something_went_wrong')}</Alert>
<OLNotification
type="error"
content={t('generic_something_went_wrong')}
/>
)}
{data && (
<Grid fluid>
<div className="container-fluid">
{data.messages && (
<Row>
<Col xs={12}>
<Alert bsStyle="danger">
<OLRow>
<OLCol xs={12}>
<OLNotification
type="error"
content={
<p style={{ whiteSpace: 'pre-wrap' }}>{data.messages}</p>
</Alert>
</Col>
</Row>
}
/>
</OLCol>
</OLRow>
)}
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<div className="pull-right">{t('total_words')}:</div>
</Col>
<Col xs={6}>{data.textWords}</Col>
</Row>
</OLCol>
<OLCol xs={6}>{data.textWords}</OLCol>
</OLRow>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<div className="pull-right">{t('headers')}:</div>
</Col>
<Col xs={6}>{data.headers}</Col>
</Row>
</OLCol>
<OLCol xs={6}>{data.headers}</OLCol>
</OLRow>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<div className="pull-right">{t('math_inline')}:</div>
</Col>
<Col xs={6}>{data.mathInline}</Col>
</Row>
</OLCol>
<OLCol xs={6}>{data.mathInline}</OLCol>
</OLRow>
<Row>
<Col xs={4}>
<OLRow>
<OLCol xs={4}>
<div className="pull-right">{t('math_display')}:</div>
</Col>
<Col xs={6}>{data.mathDisplay}</Col>
</Row>
</Grid>
</OLCol>
<OLCol xs={6}>{data.mathDisplay}</OLCol>
</OLRow>
</div>
)}
</Modal.Body>
</OLModalBody>
<Modal.Footer>
<Button bsStyle={null} className="btn-secondary" onClick={handleHide}>
<OLModalFooter>
<OLButton variant="secondary" onClick={handleHide}>
{t('close')}
</Button>
</Modal.Footer>
</OLButton>
</OLModalFooter>
</>
)
}

View file

@ -1,22 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import WordCountModalContent from './word-count-modal-content'
import AccessibleModal from '../../../shared/components/accessible-modal'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import OLModal from '@/features/ui/components/ol/ol-modal'
const WordCountModal = React.memo(function WordCountModal({
show,
handleHide,
}) {
return (
<AccessibleModal
animation
show={show}
onHide={handleHide}
id="word-count-modal"
>
<OLModal animation show={show} onHide={handleHide} id="word-count-modal">
<WordCountModalContent handleHide={handleHide} />
</AccessibleModal>
</OLModal>
)
})

View file

@ -3,13 +3,18 @@ import Icon from './icon'
import { useEffect, useState } from 'react'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { Spinner } from 'react-bootstrap-5'
import classNames from 'classnames'
function LoadingSpinner({
align,
delay = 0,
loadingText,
size,
}: {
align?: 'left' | 'center'
delay?: 0 | 500 // 500 is our standard delay
loadingText?: string
size?: 'sm'
}) {
const { t } = useTranslation()
@ -29,6 +34,9 @@ function LoadingSpinner({
return null
}
const alignmentClass =
align === 'left' ? 'align-items-start' : 'align-items-center'
return (
<BootstrapVersionSwitcher
bs3={
@ -39,12 +47,13 @@ function LoadingSpinner({
</div>
}
bs5={
<div className="text-center mt-4">
<div className={classNames(`d-flex ${alignmentClass}`)}>
<Spinner
animation="border"
aria-hidden="true"
role="status"
className="align-middle"
className="align-self-center"
size={size}
/>
&nbsp;
{loadingText || t('loading')}

View file

@ -6,6 +6,7 @@ type IconProps = React.ComponentProps<'i'> & {
type: string
accessibilityLabel?: string
modifier?: string
size?: '2x'
}
function MaterialIcon({
@ -13,9 +14,12 @@ function MaterialIcon({
className,
accessibilityLabel,
modifier,
size,
...rest
}: IconProps) {
const iconClassName = classNames('material-symbols', className, modifier)
const iconClassName = classNames('material-symbols', className, modifier, {
[`size-${size}`]: size,
})
return (
<>

View file

@ -0,0 +1,46 @@
function DropboxlLogoBlack() {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_5804_191)">
<path
d="M5.41651 2.16667L0.833374 5.08661L5.41651 8.00655L10.0004 5.08661L5.41651 2.16667Z"
fill="currentColor"
/>
<path
d="M14.5836 2.16667L10.0005 5.08661L14.5836 8.00655L19.1668 5.08661L14.5836 2.16667Z"
fill="currentColor"
/>
<path
d="M0.833374 10.9265L5.41651 13.8464L10.0004 10.9265L5.41651 8.00655L0.833374 10.9265Z"
fill="currentColor"
/>
<path
d="M14.5836 8.00655L10.0005 10.9265L14.5836 13.8464L19.1668 10.9265L14.5836 8.00655Z"
fill="currentColor"
/>
<path
d="M5.4165 14.8198L10.0004 17.7397L14.5836 14.8198L10.0004 11.8998L5.4165 14.8198Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_5804_191">
<rect
width="18.3333"
height="15.573"
fill="white"
transform="translate(0.833374 2.16667)"
/>
</clipPath>
</defs>
</svg>
)
}
export default DropboxlLogoBlack

View file

@ -0,0 +1,30 @@
function GitFork() {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_5804_193)">
<path
d="M18.6495 9.20689L10.795 1.35338C10.3431 0.901045 9.60947 0.901045 9.15698 1.35338L7.52609 2.98443L9.59482 5.05317C10.0758 4.89079 10.627 4.99966 11.0102 5.38292C11.3953 5.76856 11.5034 6.32451 11.337 6.80705L13.3309 8.80094C13.8133 8.63471 14.3698 8.74211 14.755 9.12798C15.2935 9.66633 15.2935 10.5387 14.755 11.0772C14.2164 11.6159 13.3441 11.6159 12.8052 11.0772C12.4002 10.672 12.3001 10.077 12.5052 9.5781L10.6457 7.71858L10.6455 12.6119C10.7768 12.6769 10.9007 12.7636 11.0101 12.8726C11.5485 13.4109 11.5485 14.2831 11.0101 14.8222C10.4716 15.3605 9.59883 15.3605 9.06088 14.8222C8.52245 14.2831 8.52245 13.4109 9.06088 12.8726C9.19395 12.7397 9.34792 12.6392 9.51221 12.5718V7.63301C9.34784 7.56591 9.19405 7.46612 9.06079 7.33222C8.65295 6.9247 8.55472 6.32612 8.76388 5.82529L6.72452 3.78561L1.33936 9.17061C0.88688 9.62329 0.88688 10.3569 1.33936 10.8092L9.19336 18.6628C9.64554 19.1151 10.3789 19.1151 10.8317 18.6628L18.6494 10.8463C19.1017 10.3938 19.1017 9.65999 18.6494 9.20756"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_5804_193">
<rect
width="18"
height="18"
fill="white"
transform="translate(1 1)"
/>
</clipPath>
</defs>
</svg>
)
}
export default GitFork

View file

@ -0,0 +1,48 @@
function GithubLogoBlack() {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.8325 2C5.23119 2 1.5 5.73036 1.5 10.3325C1.5 14.0139 3.88752 17.1373 7.19827 18.239C7.6147 18.3162 7.76759 18.0583 7.76759 17.8382C7.76759 17.6396 7.75986 16.9831 7.75627 16.2869C5.43816 16.7909 4.94902 15.3037 4.94902 15.3037C4.56998 14.3408 4.02384 14.0845 4.02384 14.0845C3.26783 13.5674 4.08083 13.578 4.08083 13.578C4.91756 13.6368 5.35813 14.4368 5.35813 14.4368C6.10131 15.7105 7.30742 15.3422 7.7829 15.1293C7.85783 14.5909 8.07363 14.2232 8.31193 14.0151C6.46117 13.8046 4.51561 13.0899 4.51561 9.89716C4.51561 8.98758 4.84111 8.24413 5.37414 7.6606C5.28763 7.45073 5.00242 6.60324 5.455 5.4555C5.455 5.4555 6.15471 5.23169 7.74689 6.30975C8.41155 6.12513 9.12437 6.03254 9.8325 6.02923C10.5406 6.03254 11.254 6.12513 11.9199 6.30975C13.5103 5.23169 14.209 5.4555 14.209 5.4555C14.6626 6.60324 14.3774 7.45073 14.2909 7.6606C14.8251 8.24413 15.1483 8.98758 15.1483 9.89716C15.1483 13.0977 13.199 13.8022 11.3435 14.0085C11.6424 14.2671 11.9087 14.7742 11.9087 15.5516C11.9087 16.6665 11.8992 17.5638 11.8992 17.8382C11.8992 18.0601 12.0491 18.3198 12.4714 18.2381C15.7805 17.135 18.165 14.0128 18.165 10.3325C18.165 5.73036 14.4344 2 9.8325 2Z"
fill="currentColor"
/>
<path
d="M4.65591 13.9635C4.63756 14.0051 4.57243 14.0175 4.51309 13.9891C4.45252 13.9617 4.41871 13.9053 4.43831 13.8638C4.45625 13.8213 4.52137 13.8094 4.58181 13.8378C4.64225 13.865 4.67674 13.922 4.65591 13.9635Z"
fill="currentColor"
/>
<path
d="M4.99353 14.34C4.95379 14.3769 4.87597 14.3598 4.8234 14.3015C4.76875 14.2434 4.75868 14.1656 4.79897 14.1284C4.83995 14.0915 4.91529 14.1088 4.9698 14.1669C5.02444 14.2256 5.03506 14.3026 4.99353 14.34Z"
fill="currentColor"
/>
<path
d="M5.32207 14.8199C5.27102 14.8555 5.1874 14.8222 5.13579 14.7481C5.08474 14.6739 5.08474 14.585 5.13703 14.5494C5.18864 14.5138 5.27102 14.546 5.32331 14.6194C5.37423 14.6947 5.37423 14.7836 5.32207 14.8199Z"
fill="currentColor"
/>
<path
d="M5.77213 15.2835C5.72646 15.3339 5.62918 15.3204 5.55798 15.2515C5.48512 15.1845 5.46484 15.0891 5.51051 15.0388C5.55688 14.9883 5.65471 15.0025 5.72646 15.0706C5.79876 15.1377 5.8207 15.2337 5.77213 15.2835Z"
fill="currentColor"
/>
<path
d="M6.3931 15.5528C6.37282 15.618 6.27927 15.6475 6.18489 15.6198C6.09064 15.5913 6.02897 15.5149 6.04801 15.449C6.0676 15.3833 6.16171 15.3524 6.25678 15.3821C6.35088 15.4105 6.4127 15.4864 6.3931 15.5528Z"
fill="currentColor"
/>
<path
d="M7.075 15.6026C7.07735 15.6713 6.99732 15.7283 6.89824 15.7296C6.79862 15.7318 6.7179 15.6762 6.7168 15.6086C6.7168 15.5392 6.79517 15.4829 6.89466 15.4811C6.99373 15.4791 7.075 15.5345 7.075 15.6026Z"
fill="currentColor"
/>
<path
d="M7.70952 15.4947C7.72138 15.5616 7.65253 15.6305 7.55415 15.6488C7.45742 15.6666 7.36787 15.6251 7.35559 15.5587C7.34358 15.49 7.41354 15.4211 7.51027 15.4033C7.60879 15.3862 7.69696 15.4265 7.70952 15.4947Z"
fill="currentColor"
/>
</svg>
)
}
export default GithubLogoBlack

View file

@ -247,12 +247,8 @@ $dropdown-item-padding-x: var(--spacing-04);
$dropdown-item-padding-y: var(--spacing-05);
$dropdown-header-color: var(--content-secondary);
// List group
$list-group-color: var(--content-secondary);
$list-group-border-width: 0;
$list-group-border-radius: (--border-radius-base);
$list-group-item-padding-y: var(--spacing-04);
$list-group-item-padding-x: var(--spacing-05);
$list-group-hover-bg: var(--bg-light-secondary);
$list-group-disabled-color: var(--content-disabled);
$list-group-disabled-bg: var(--bg-light-primary);
// Offcanvas
$offcanvas-horizontal-width: 320px;
$offcanvas-padding-x: 12.5px;
$offcanvas-padding-y: 12.5px;
$offcanvas-border-width: 0;

View file

@ -42,6 +42,7 @@
@import 'bootstrap-5/scss/close';
@import 'bootstrap-5/scss/nav';
@import 'bootstrap-5/scss/navbar';
@import 'bootstrap-5/scss/offcanvas';
// Helpers
@import 'bootstrap-5/scss/helpers';

View file

@ -54,7 +54,3 @@ hr {
.row-spaced-extra-large {
margin-top: calc(var(--line-height-03) * 4);
}
.rotate-180 {
transform: rotate(180deg);
}

View file

@ -78,6 +78,10 @@ samp {
text-align: center;
}
.text-muted {
color: var(--content-disabled) !important;
}
@include media-breakpoint-up(lg) {
.text-center-only-desktop {
text-align: center;

View file

@ -24,6 +24,7 @@
@import 'beta-badges';
@import 'list-group';
@import 'select';
@import 'dictionary';
@import 'link';
@import 'pagination';
@import 'loading-spinner';

View file

@ -0,0 +1,33 @@
#dictionary-modal {
.modal-body {
padding: 0;
}
}
.dictionary-entries-list {
overflow-y: scroll;
max-height: calc(100vh - 225px);
margin: 0;
padding: var(--spacing-04);
}
.dictionary-entry {
word-break: break-all;
display: flex;
padding: var(--spacing-04);
border-bottom: solid 1px var(--border-divider);
align-items: center;
&:last-child {
border-bottom: none;
}
}
.dictionary-entry-name {
flex-grow: 1;
padding-right: var(--spacing-02);
}
.dictionary-empty-body {
padding: var(--spacing-07);
}

View file

@ -6,12 +6,14 @@
@import 'editor/ide';
@import 'editor/toolbar';
@import 'editor/online-users';
@import 'editor/review-panel';
@import 'editor/hotkeys';
@import 'editor/left-menu';
@import 'editor/loading-screen';
@import 'editor/outline';
@import 'editor/file-tree';
@import 'editor/file-view';
@import 'editor/figure-modal';
@import 'editor/review-panel';
@import 'editor/chat';
@import 'subscription';
@import 'editor/pdf';

View file

@ -29,7 +29,7 @@
.sales-contact-form-left-column {
.sales-contact-form-heading-title {
font-size: 2.25rem;
font-family: var(-font-family-san-serif);
font-family: var(--font-family-san-serif);
font-style: normal;
font-weight: 600;
line-height: 1.333;

View file

@ -0,0 +1,30 @@
.hotkeys-modal {
font-size: var(--font-size-02);
h3:first-child {
margin-top: 0;
}
.hotkey {
margin-bottom: var(--spacing-05);
}
.description {
display: inline-block;
}
.combination {
padding: var(--spacing-02) var(--spacing-04);
border-radius: 3px;
background-color: var(--bg-dark-tertiary);
color: var(--white);
font-weight: 600;
margin-right: var(--spacing-03);
}
.hotkeys-modal-bottom-text {
background-color: var(--bg-light-secondary);
padding: var(--spacing-04);
border-radius: var(--border-radius-base);
}
}

View file

@ -193,3 +193,31 @@
position: absolute;
inset: 0;
}
.teaser-title {
margin-top: 0;
text-align: center;
}
.teaser-refresh-label {
text-align: center;
}
.teaser-img {
display: block;
max-width: 100%;
height: auto;
margin-bottom: var(--spacing-03);
}
.teaser-video-container {
margin: calc(var(--spacing-07) * -1) calc(var(--spacing-07) * -1)
var(--spacing-02) calc(var(--spacing-07) * -1);
overflow: hidden;
}
.teaser-video {
width: 100%;
height: auto;
border-bottom: 1px solid var(--border-divider);
}

View file

@ -0,0 +1,247 @@
:root {
--left-menu-form-select-border: var(--border-primary);
}
@include theme('light') {
--left-menu-form-select-border: var(--border-disabled);
}
.left-menu {
position: absolute;
top: 0;
bottom: 0;
background-color: var(--bg-light-secondary);
z-index: 100;
overflow: hidden auto;
transition: left ease-in-out 0.5s;
font-size: var(--font-size-02);
width: 320px;
&.shown {
left: 0;
}
h4 {
font-family: $font-family-sans-serif;
font-weight: 400;
font-size: var(--font-size-03);
margin: var(--spacing-05) 0;
padding-bottom: var(--spacing-03);
color: var(--content-secondary);
border-bottom: 1px solid var(--border-primary-dark);
&:first-child {
margin-top: 0;
}
}
ul.nav {
.left-menu-button {
cursor: pointer;
padding: var(--spacing-03);
font-weight: 700;
color: var(--link-ui);
display: flex;
align-items: center;
width: 100%;
background-color: inherit;
border: none;
text-decoration: none;
.material-symbols {
margin-right: var(--spacing-04);
color: var(--neutral-70);
}
&:hover,
&:active {
background-color: var(--bg-info-01);
color: var(--white);
.material-symbols {
color: var(--white);
}
}
}
a {
cursor: pointer;
&:hover,
&:active,
&:focus {
background-color: var(--bg-info-01);
color: var(--white);
.material-symbols {
color: var(--white);
}
}
.material-symbols {
color: var(--neutral-70);
}
padding: var(--spacing-03);
font-weight: 700;
}
.link-disabled {
color: var(--content-disabled);
}
}
> ul.nav:last-child {
margin-bottom: var(--spacing-05);
}
ul.nav-downloads {
li {
display: inline-block;
text-align: center;
width: 100px;
a {
color: var(--content-secondary);
text-decoration: none;
}
.material-symbols {
margin: var(--spacing-03) 0;
}
}
}
form.settings {
label {
font-weight: normal;
color: var(--content-secondary);
flex: 1 0 50%;
margin-bottom: 0;
padding-right: var(--spacing-03);
white-space: nowrap;
}
button,
select {
width: 50%;
margin: var(--spacing-04) 0;
}
}
.left-menu-setting {
padding: 0 var(--spacing-02);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
border-bottom: 1px solid rgb(0 0 0 / 7%);
margin-bottom: 0;
height: 43px;
&:first-child {
margin-top: calc(var(--spacing-04) * -1);
}
&:last-child {
border-bottom: 0;
}
&:hover {
background-color: var(--bg-info-01);
label {
color: var(--white);
}
}
.form-select {
border: 1px solid var(--left-menu-form-select-border);
}
}
}
#left-menu-mask {
opacity: 0.4;
background-color: #999;
z-index: 99;
transition: opacity 0.5s;
position: absolute;
inset: 0;
}
.left-menu-modal-backdrop {
background-color: transparent;
}
.nav {
margin-bottom: 0;
padding-left: 0;
list-style: none;
display: block;
> li {
position: relative;
display: block;
> a {
position: relative;
display: block;
padding: var(--spacing-04) var(--spacing-06);
&:hover,
&:focus {
text-decoration: none;
background-color: var(--bg-info-01);
color: white;
}
}
// Disabled state sets text to gray and nukes hover/tab effects
&.disabled > a {
color: var(--content-disabled);
&:hover,
&:focus {
color: var(--content-disabled);
text-decoration: none;
background-color: transparent;
cursor: not-allowed;
}
}
}
// Open dropdowns
.open > a {
&,
&:hover,
&:focus {
background-color: var(--bg-info-01);
border-color: var(--link-ui);
}
}
}
.loading-spinner-container {
display: flex;
align-items: center;
justify-content: center;
}
.full-size-loading-spinner-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.reference-manager-search-group {
width: '100%';
border-radius: 0;
text-align: left;
.dropdown-menu {
width: 100%;
}
}

View file

@ -486,7 +486,7 @@ describe('<EditorLeftMenu />', function () {
</EditorProviders>
)
cy.get('label[for="dictionary"] ~ button').click()
cy.get('label[for="dictionary-settings"] ~ button').click()
cy.findByText('Edit Dictionary')
cy.findByText('Your custom dictionary is empty.')
})

View file

@ -9,7 +9,7 @@ describe('<SettingsDictionary />', function () {
screen.getByText('Dictionary')
const button = screen.getByRole('button', { name: 'Edit' })
const button = screen.getByText('Edit')
fireEvent.click(button)
const modal = screen.getAllByRole('dialog')[0]