Merge pull request #21477 from overleaf/ii-bs5-table-generator

[web] BS5 table generator

GitOrigin-RevId: 3fe10c05fa36f026c47ff4f54b15f5d76f7c509e
This commit is contained in:
ilkin-overleaf 2024-11-01 14:10:50 +02:00 committed by Copybot
parent ba7d11d854
commit 1033e73844
12 changed files with 238 additions and 224 deletions

View file

@ -1,9 +1,8 @@
import MaterialIcon from '@/shared/components/material-icon'
import { WidthSelection } from './toolbar/column-width-modal/column-width'
import { useMemo } from 'react'
import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/shared/components/tooltip'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { useSelectionContext } from './contexts/selection-context'
function roundIfNeeded(width: number) {
@ -32,7 +31,7 @@ export const ColumnSizeIndicator = ({
}
return (
<Tooltip
<OLTooltip
id="tooltip-column-width-button"
description={
unit === 'custom'
@ -43,9 +42,8 @@ export const ColumnSizeIndicator = ({
}
overlayProps={{ delay: 0, placement: 'bottom' }}
>
<Button
bsStyle={null}
className="table-generator-column-indicator-button"
<button
className="btn table-generator-column-indicator-button"
onClick={onClick}
>
<MaterialIcon
@ -55,7 +53,7 @@ export const ColumnSizeIndicator = ({
<span className="table-generator-column-indicator-label">
{formattedWidth}
</span>
</Button>
</Tooltip>
</button>
</OLTooltip>
)
}

View file

@ -1,5 +1,10 @@
import { Button, Modal } from 'react-bootstrap'
import AccessibleModal from '../../../../shared/components/accessible-modal'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
import OLButton from '@/features/ui/components/ol/ol-button'
import { useTabularContext } from './contexts/tabular-context'
import { Trans, useTranslation } from 'react-i18next'
@ -9,15 +14,15 @@ export const TableGeneratorHelpModal = () => {
if (!helpShown) return null
return (
<AccessibleModal
<OLModal
show={helpShown}
onHide={hideHelp}
className="table-generator-help-modal"
>
<Modal.Header closeButton>
<Modal.Title>{t('help')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<OLModalHeader closeButton>
<OLModalTitle>{t('help')}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
<Trans
i18nKey="this_tool_helps_you_insert_simple_tables_into_your_project_without_writing_latex_code_give_feedback"
@ -83,10 +88,12 @@ export const TableGeneratorHelpModal = () => {
]}
/>
</p>
</Modal.Body>
<Modal.Footer>
<Button onClick={hideHelp}>{t('close')}</Button>
</Modal.Footer>
</AccessibleModal>
</OLModalBody>
<OLModalFooter>
<OLButton variant="secondary" onClick={hideHelp}>
{t('close')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}

View file

@ -14,7 +14,6 @@ import {
} from './contexts/editing-context'
import { EditorView } from '@codemirror/view'
import { ErrorBoundary } from 'react-error-boundary'
import { Alert, Button } from 'react-bootstrap'
import { EditorSelection } from '@codemirror/state'
import {
CodeMirrorViewContext,
@ -22,13 +21,14 @@ import {
} from '../codemirror-context'
import { TableProvider } from './contexts/table-context'
import { TabularProvider, useTabularContext } from './contexts/tabular-context'
import Icon from '../../../../shared/components/icon'
import { BorderTheme } from './toolbar/commands'
import { TableGeneratorHelpModal } from './help-modal'
import { SplitTestProvider } from '../../../../shared/context/split-test-context'
import { useTranslation } from 'react-i18next'
import { ColumnWidthModal } from './toolbar/column-width-modal/modal'
import { WidthSelection } from './toolbar/column-width-modal/column-width'
import Notification from '@/shared/components/notification'
import OLButton from '@/features/ui/components/ol/ol-button'
export type ColumnDefinition = {
alignment: 'left' | 'center' | 'right' | 'paragraph'
@ -198,35 +198,40 @@ export const TableRenderingError: FC<{
codePosition?: number
}> = ({ view, codePosition }) => {
const { t } = useTranslation()
return (
<Alert className="table-generator-error">
<span className="table-generator-error-icon">
<Icon type="exclamation-circle" />
</span>
<div className="table-generator-error-message">
<p className="table-generator-error-message-header">
<Notification
type="info"
content={
<>
<p>
<strong>
{t('sorry_your_table_cant_be_displayed_at_the_moment')}
</strong>
</p>
<p>
{t(
'this_could_be_because_we_cant_support_some_elements_of_the_table'
)}
</p>
</div>
{codePosition !== undefined && (
<Button
bsStyle={null}
className="btn-secondary table-generator-error-show-code-button"
</>
}
action={
codePosition !== undefined ? (
<OLButton
variant="secondary"
onClick={() =>
view.dispatch({
selection: EditorSelection.cursor(codePosition),
})
}
size="sm"
>
{t('view_code')}
</Button>
)}
</Alert>
</OLButton>
) : undefined
}
/>
)
}

View file

@ -1,5 +1,3 @@
import AccessibleModal from '@/shared/components/accessible-modal'
import { Button, Modal, Form, FormGroup } from 'react-bootstrap'
import { useTabularContext } from '../../contexts/tabular-context'
import { Select } from '@/shared/components/select'
import { Trans, useTranslation } from 'react-i18next'
@ -18,8 +16,24 @@ import { setColumnWidth } from '../commands'
import { UNITS, WidthSelection, WidthUnit } from './column-width'
import { useCodeMirrorViewContext } from '../../../codemirror-context'
import { CopyToClipboard } from '@/shared/components/copy-to-clipboard'
import Tooltip from '@/shared/components/tooltip'
import Icon from '@/shared/components/icon'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
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'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import OLCol from '@/features/ui/components/ol/ol-col'
import OLRow from '@/features/ui/components/ol/ol-row'
import OLForm from '@/features/ui/components/ol/ol-form'
import { bsVersion } from '@/features/utils/bootstrap-5'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type UnitDescription = { label: string; tooltip?: string } | undefined
@ -93,7 +107,7 @@ const ColumnWidthModalBody = () => {
}
}, [columnWidthModalShown, selection, table])
const onSubmit: FormEventHandler<Form> = useCallback(
const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
e => {
e.preventDefault()
if (selection && currentUnit) {
@ -121,35 +135,34 @@ const ColumnWidthModalBody = () => {
)
return (
<AccessibleModal
<OLModal
show={columnWidthModalShown}
onHide={closeColumnWidthModal}
className="table-generator-width-modal"
>
<Form onSubmit={onSubmit}>
<Modal.Header closeButton>
<Modal.Title>{t('set_column_width')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="clearfix">
<FormGroup className="col-md-8 p-0 mb-0">
<label
className="table-generator-width-label"
htmlFor="column-width-modal-width"
<OLModalHeader closeButton>
<OLModalTitle>{t('set_column_width')}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<OLForm id="table-generator-width-form" onSubmit={onSubmit}>
<OLRow className={bsVersion({ bs5: 'g-3' })}>
<OLCol lg={8}>
<OLFormGroup
controlId="column-width-modal-width"
className="mb-0"
>
{t('column_width')}
</label>
<input
id="column-width-modal-width"
<OLFormLabel>{t('column_width')}</OLFormLabel>
<OLFormControl
value={currentWidth}
required
onChange={e => setCurrentWidth(e.target.value)}
type={currentUnit === 'custom' ? 'text' : 'number'}
className="form-control"
ref={inputRef}
/>
</FormGroup>
<FormGroup className="col-md-4 mb-0">
</OLFormGroup>
</OLCol>
<OLCol lg={4}>
<OLFormGroup className="mb-0">
<Select
label={
<>
@ -162,19 +175,27 @@ const ColumnWidthModalBody = () => {
onSelectedItemChanged={item => setCurrentUnit(item)}
defaultItem={currentUnit}
/>
</FormGroup>
</div>
</OLFormGroup>
</OLCol>
</OLRow>
{unitHelp && (
<p className="my-1">
{unitHelp.label}{' '}
{unitHelp.tooltip && (
<Tooltip
<OLTooltip
id="table-generator-unit-tooltip"
description={unitHelp.tooltip}
overlayProps={{ delay: 0, placement: 'top' }}
>
<Icon type="question-circle" fw />
</Tooltip>
<span>
<BootstrapVersionSwitcher
bs3={<Icon type="question-circle" />}
bs5={
<MaterialIcon type="help" className="align-middle" />
}
/>
</span>
</OLTooltip>
)}
</p>
)}
@ -196,22 +217,25 @@ const ColumnWidthModalBody = () => {
tooltipId="table-generator-array-copy"
/>
</div>
</Modal.Body>
<Modal.Footer>
<Button
bsStyle={null}
className="btn-secondary"
</OLForm>
</OLModalBody>
<OLModalFooter>
<OLButton
variant="secondary"
onClick={() => {
closeColumnWidthModal()
}}
>
{t('cancel')}
</Button>
<Button bsStyle={null} className="btn-primary" type="submit">
</OLButton>
<OLButton
variant="primary"
form="table-generator-width-form"
type="submit"
>
{t('ok')}
</Button>
</Modal.Footer>
</Form>
</AccessibleModal>
</OLButton>
</OLModalFooter>
</OLModal>
)
}

View file

@ -1,9 +1,12 @@
import { FC, memo, useRef } from 'react'
import useDropdown from '../../../../../shared/hooks/use-dropdown'
import { ListGroup, Overlay, Popover } from 'react-bootstrap'
import Tooltip from '../../../../../shared/components/tooltip'
import OLListGroup from '@/features/ui/components/ol/ol-list-group'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import MaterialIcon from '../../../../../shared/components/material-icon'
import { useTabularContext } from '../contexts/tabular-context'
import { bsVersion } from '@/features/utils/bootstrap-5'
export const ToolbarButtonMenu: FC<{
id: string
@ -45,36 +48,37 @@ export const ToolbarButtonMenu: FC<{
)
const overlay = tableContainerRef.current && (
<Overlay
<OLOverlay
show={open}
target={target.current}
placement="bottom"
container={tableContainerRef.current}
containerPadding={0}
animation
transition
rootClose
onHide={() => onToggle(false)}
>
<Popover
<OLPopover
id={`${id}-menu`}
ref={ref}
className="table-generator-button-menu-popover"
>
<ListGroup
<OLListGroup
role="menu"
onClick={() => {
onToggle(false)
}}
className={bsVersion({ bs5: 'd-block' })}
>
{children}
</ListGroup>
</Popover>
</Overlay>
</OLListGroup>
</OLPopover>
</OLOverlay>
)
return (
<>
<Tooltip
<OLTooltip
hidden={open}
id={id}
description={
@ -83,7 +87,7 @@ export const ToolbarButtonMenu: FC<{
overlayProps={{ placement: 'bottom' }}
>
{button}
</Tooltip>
</OLTooltip>
{overlay}
</>
)

View file

@ -1,7 +1,7 @@
import { EditorView } from '@codemirror/view'
import classNames from 'classnames'
import { memo, useCallback } from 'react'
import Tooltip from '../../../../../shared/components/tooltip'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import MaterialIcon from '../../../../../shared/components/material-icon'
import { useCodeMirrorViewContext } from '../../codemirror-context'
import { emitTableGeneratorEvent } from '../analytics'
@ -64,12 +64,12 @@ export const ToolbarButton = memo<{
disabled && disabledLabel ? <div>{disabledLabel}</div> : <div>{label}</div>
return (
<Tooltip
<OLTooltip
id={id}
description={description}
overlayProps={{ placement: 'bottom' }}
>
{button}
</Tooltip>
</OLTooltip>
)
})

View file

@ -1,8 +1,9 @@
import { ButtonHTMLAttributes, FC, useCallback, useRef } from 'react'
import useDropdown from '../../../../../shared/hooks/use-dropdown'
import { Overlay, Popover } from 'react-bootstrap'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
import MaterialIcon from '../../../../../shared/components/material-icon'
import Tooltip from '../../../../../shared/components/tooltip'
import { useTabularContext } from '../contexts/tabular-context'
import { emitTableGeneratorEvent } from '../analytics'
import { useCodeMirrorViewContext } from '../../codemirror-context'
@ -55,17 +56,17 @@ export const ToolbarDropdown: FC<{
</button>
)
const overlay = tabularRef.current && (
<Overlay
<OLOverlay
show={open}
target={toggleButtonRef.current ?? undefined}
target={toggleButtonRef.current}
placement="bottom"
container={tabularRef.current}
animation
transition
rootClose
containerPadding={0}
onHide={() => onToggle(false)}
>
<Popover
<OLPopover
id={`${id}-popover`}
ref={ref}
className="table-generator-toolbar-dropdown-popover"
@ -82,21 +83,21 @@ export const ToolbarDropdown: FC<{
>
{children}
</div>
</Popover>
</Overlay>
</OLPopover>
</OLOverlay>
)
if (tooltip || (disabled && disabledTooltip)) {
return (
<>
<Tooltip
<OLTooltip
hidden={open}
id={id}
description={disabled && disabledTooltip ? disabledTooltip : tooltip}
overlayProps={{ placement: 'bottom' }}
>
{button}
</Tooltip>
</OLTooltip>
{overlay}
</>
)

View file

@ -1,4 +1,5 @@
import { EditorView } from '@codemirror/view'
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
export const tableGeneratorTheme = EditorView.baseTheme({
'&dark .table-generator': {
@ -309,14 +310,14 @@ export const tableGeneratorTheme = EditorView.baseTheme({
'.table-generator-button-menu-popover': {
'background-color': 'var(--table-generator-toolbar-background) !important',
'& .popover-content': {
'& .popover-content, & .popover-body': {
padding: '4px',
},
'& .list-group': {
margin: '0',
padding: '0',
},
'& > .arrow': {
'& > .arrow, & > .popover-arrow': {
display: 'none',
},
},
@ -348,7 +349,7 @@ export const tableGeneratorTheme = EditorView.baseTheme({
gap: '6px',
'align-items': 'flex-start',
'max-width': '240px',
'font-family': 'Lato',
'font-family': isBootstrap5() ? 'var(--bs-body-font-family)' : 'Lato',
'& .info-icon': {
flex: ' 0 0 24px',
@ -369,7 +370,7 @@ export const tableGeneratorTheme = EditorView.baseTheme({
display: 'flex',
'align-items': 'center',
'justify-content': 'space-between',
'font-family': 'Lato',
'font-family': isBootstrap5() ? 'var(--bs-body-font-family)' : 'Lato',
height: '36px',
'&:not(:first-child)': {
@ -387,11 +388,11 @@ export const tableGeneratorTheme = EditorView.baseTheme({
'max-width': '300px',
background: 'var(--table-generator-toolbar-background) !important',
'& .popover-content': {
'& .popover-content, & .popover-body': {
padding: '0',
},
'& > .arrow': {
'& > .arrow, & > .popover-arrow': {
display: 'none',
},
},
@ -416,7 +417,7 @@ export const tableGeneratorTheme = EditorView.baseTheme({
'column-gap': '8px',
'align-self': 'stretch',
padding: '12px 8px',
'font-family': 'Lato',
'font-family': isBootstrap5() ? 'var(--bs-body-font-family)' : 'Lato',
'& .table-generator-button-label': {
'align-self': 'stretch',
@ -482,30 +483,7 @@ export const tableGeneratorTheme = EditorView.baseTheme({
'.ol-cm-environment-table.table-generator-error-container, .ol-cm-environment-table.ol-cm-tabular':
{
background: 'rgba(125, 125, 125, 0.05)',
},
'.table-generator-error': {
background: 'var(--table-generator-error-background)',
display: 'flex',
'justify-content': 'space-between',
color: 'var(--table-generator-error-color)',
border: '1px solid var(--table-generator-error-border-color)',
'font-family': 'Lato',
margin: '0 16px 0 16px',
'& .table-generator-error-message': {
flex: '1 1 auto',
},
'& .table-generator-error-message-header': {
fontWeight: 'bold',
marginBottom: '2px',
},
'& .table-generator-error-show-code-button': {
alignSelf: 'baseline',
},
'& .table-generator-error-icon': {
color: '#3265B2',
'margin-right': '12px',
},
'font-family': isBootstrap5() ? 'var(--bs-body-font-family)' : 'Lato',
},
'.table-generator-filler-row': {

View file

@ -10,28 +10,34 @@ export class TableRenderingErrorWidget extends WidgetType {
toDOM(view: EditorView): HTMLElement {
const warning = document.createElement('div')
warning.classList.add('table-generator-error', 'alert')
warning.classList.add('notification', 'notification-type-info')
warning.role = 'alert'
const icon = document.createElement('span')
icon.classList.add('table-generator-error-icon')
const iconType = document.createElement('i')
iconType.classList.add('fa', 'fa-info-circle')
const icon = document.createElement('div')
icon.classList.add('notification-icon')
const iconType = document.createElement('span')
iconType.classList.add('material-symbols')
iconType.setAttribute('aria-hidden', 'true')
iconType.textContent = 'info'
icon.appendChild(iconType)
warning.appendChild(icon)
const messageWrapper = document.createElement('div')
messageWrapper.classList.add('notification-content-and-cta')
const message = document.createElement('div')
message.classList.add('table-generator-error-message')
message.classList.add('notification-content')
const messageHeader = document.createElement('p')
messageHeader.classList.add('table-generator-error-message-header')
messageHeader.textContent = view.state.phrase(
const messageHeaderInner = document.createElement('strong')
messageHeaderInner.textContent = view.state.phrase(
'sorry_your_table_cant_be_displayed_at_the_moment'
)
messageHeader.appendChild(messageHeaderInner)
const messageBody = document.createElement('p')
messageBody.textContent = view.state.phrase(
'this_could_be_because_we_cant_support_some_elements_of_the_table'
)
message.appendChild(messageHeader)
message.appendChild(messageBody)
warning.appendChild(message)
messageWrapper.appendChild(message)
warning.appendChild(messageWrapper)
const element = document.createElement('div')
element.classList.add('table-generator', 'table-generator-error-container')
element.appendChild(warning)

View file

@ -1,4 +1,4 @@
import { FC, memo, MouseEventHandler, useCallback, useState } from 'react'
import { memo, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
@ -33,24 +33,7 @@ export const CopyToClipboard = memo<{
description={copied ? `${t('copied')}!` : t('copy')}
overlayProps={{ delay: copied ? 1000 : 250 }}
>
<span>
{kind === 'text' ? (
<TextButton handleClick={handleClick} />
) : (
<IconButton handleClick={handleClick} copied={copied} />
)}
</span>
</OLTooltip>
)
})
CopyToClipboard.displayName = 'CopyToClipboard'
const TextButton: FC<{
handleClick: MouseEventHandler<HTMLButtonElement>
}> = ({ handleClick }) => {
const { t } = useTranslation()
return (
<OLButton
onClick={handleClick}
size="sm"
@ -60,16 +43,7 @@ const TextButton: FC<{
>
{t('copy')}
</OLButton>
)
}
const IconButton: FC<{
handleClick: MouseEventHandler<HTMLButtonElement>
copied: boolean
}> = ({ handleClick, copied }) => {
const { t } = useTranslation()
return (
) : (
<OLIconButton
onClick={handleClick}
variant="link"
@ -82,5 +56,8 @@ const IconButton: FC<{
bs3: copied ? 'check' : 'clipboard',
})}
/>
)}
</OLTooltip>
)
}
})
CopyToClipboard.displayName = 'CopyToClipboard'

View file

@ -24,6 +24,7 @@
@import 'editor/share';
@import 'editor/tags-input';
@import 'editor/review-panel-new';
@import 'editor/table-generator-column-width-modal';
@import 'website-redesign';
@import 'group-settings';
@import 'templates-v2';

View file

@ -0,0 +1,13 @@
.table-generator-width-modal {
.table-generator-width-label {
width: 100%;
}
.table-generator-usepackage-copy {
display: flex;
justify-content: space-between;
padding: var(--spacing-03) var(--spacing-05);
background: var(--bg-light-secondary);
border: 1px solid var(--border-primary-dark);
}
}