mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #19027 from overleaf/ii-bs5-projects-list-table
[web] BS5 projects table migration GitOrigin-RevId: 237bd8113c68d7fd1b66712f7361eb956b1e10e7
This commit is contained in:
parent
bac35566ff
commit
c3ed95bc48
64 changed files with 1301 additions and 540 deletions
|
@ -1224,7 +1224,6 @@
|
|||
"shortcut_to_open_advanced_reference_search": "",
|
||||
"show_all": "",
|
||||
"show_all_projects": "",
|
||||
"show_all_uppercase": "",
|
||||
"show_document_preamble": "",
|
||||
"show_hotkeys": "",
|
||||
"show_in_code": "",
|
||||
|
@ -1232,7 +1231,6 @@
|
|||
"show_less": "",
|
||||
"show_local_file_contents": "",
|
||||
"show_outline": "",
|
||||
"show_x_more": "",
|
||||
"show_x_more_projects": "",
|
||||
"showing_1_result": "",
|
||||
"showing_1_result_of_total": "",
|
||||
|
|
|
@ -2,16 +2,20 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Modal,
|
||||
Alert,
|
||||
Button,
|
||||
ControlLabel,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
} from 'react-bootstrap'
|
||||
import { postJSON } from '../../../infrastructure/fetch-json'
|
||||
import { CloneProjectTag } from './clone-project-tag'
|
||||
import {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import OLForm from '@/features/ui/components/ol/ol-form'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
||||
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function CloneProjectModalContent({
|
||||
handleHide,
|
||||
|
@ -77,19 +81,15 @@ export default function CloneProjectModalContent({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('copy_project')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('copy_project')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<form id="clone-project-form" onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor="clone-project-form-name">
|
||||
{t('new_name')}
|
||||
</ControlLabel>
|
||||
|
||||
<FormControl
|
||||
id="clone-project-form-name"
|
||||
<OLModalBody>
|
||||
<OLForm id="clone-project-form" onSubmit={handleSubmit}>
|
||||
<OLFormGroup controlId="clone-project-form-name">
|
||||
<OLFormLabel>{t('new_name')}</OLFormLabel>
|
||||
<OLFormControl
|
||||
type="text"
|
||||
placeholder="New Project Name"
|
||||
required
|
||||
|
@ -97,13 +97,14 @@ export default function CloneProjectModalContent({
|
|||
onChange={event => setClonedProjectName(event.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</FormGroup>
|
||||
</OLFormGroup>
|
||||
|
||||
{clonedProjectTags.length > 0 && (
|
||||
<FormGroup className="clone-project-tag">
|
||||
<ControlLabel htmlFor="clone-project-tags-list">
|
||||
{t('tags')}:{' '}
|
||||
</ControlLabel>
|
||||
<OLFormGroup
|
||||
controlId="clone-project-tags-list"
|
||||
className="clone-project-tag mb-3"
|
||||
>
|
||||
<OLFormLabel>{t('tags')}: </OLFormLabel>
|
||||
<div role="listbox" id="clone-project-tags-list">
|
||||
{clonedProjectTags.map(tag => (
|
||||
<CloneProjectTag
|
||||
|
@ -113,37 +114,31 @@ export default function CloneProjectModalContent({
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
</FormGroup>
|
||||
</OLFormGroup>
|
||||
)}
|
||||
</form>
|
||||
</OLForm>
|
||||
|
||||
{error && (
|
||||
<Alert bsStyle="danger">
|
||||
{error.length ? error : t('generic_something_went_wrong')}
|
||||
</Alert>
|
||||
<Notification
|
||||
content={error.length ? error : t('generic_something_went_wrong')}
|
||||
type="error"
|
||||
/>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</OLModalBody>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
disabled={inFlight}
|
||||
onClick={handleHide}
|
||||
>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" disabled={inFlight} onClick={handleHide}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
disabled={inFlight || !valid}
|
||||
form="clone-project-form"
|
||||
type="submit"
|
||||
bsStyle="primary"
|
||||
disabled={inFlight || !valid}
|
||||
>
|
||||
{inFlight ? <>{t('copying')}…</> : t('copy')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { memo, useCallback, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CloneProjectModalContent from './clone-project-modal-content'
|
||||
import AccessibleModal from '../../../shared/components/accessible-modal'
|
||||
import OLModal from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
function CloneProjectModal({
|
||||
show,
|
||||
|
@ -20,7 +20,7 @@ function CloneProjectModal({
|
|||
}, [handleHide, inFlight])
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
animation
|
||||
show={show}
|
||||
onHide={onHide}
|
||||
|
@ -38,7 +38,7 @@ function CloneProjectModal({
|
|||
projectName={projectName}
|
||||
projectTags={projectTags}
|
||||
/>
|
||||
</AccessibleModal>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { useState, useCallback } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import MenuItemButton from './menu-item-button'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import CopyProjectButton from '../table/cells/action-buttons/copy-project-button'
|
||||
|
@ -13,10 +21,12 @@ import DeleteProjectButton from '../table/cells/action-buttons/delete-project-bu
|
|||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import CompileAndDownloadProjectPDFButton from '../table/cells/action-buttons/compile-and-download-project-pdf-button'
|
||||
import RenameProjectButton from '../table/cells/action-buttons/rename-project-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type ActionButtonProps = {
|
||||
project: Project
|
||||
onClick: () => void // eslint-disable-line react/no-unused-prop-types
|
||||
onClick: <T extends React.MouseEvent>(e?: T, fn?: (e?: T) => void) => void
|
||||
}
|
||||
|
||||
function CopyProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
|
@ -24,7 +34,7 @@ function CopyProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
|||
<CopyProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleOpenModal(onClick)}
|
||||
onClick={e => handleOpenModal(e, onClick)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="files-o" className="menu-item-button-icon" />{' '}
|
||||
|
@ -43,7 +53,7 @@ function CompileAndDownloadProjectPDFButtonMenuItem({
|
|||
<CompileAndDownloadProjectPDFButton project={project}>
|
||||
{(text, pendingCompile, downloadProject) => (
|
||||
<MenuItemButton
|
||||
onClick={() => downloadProject(onClick)}
|
||||
onClick={e => downloadProject(e, onClick)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
{pendingCompile ? (
|
||||
|
@ -219,7 +229,7 @@ type ActionDropdownProps = {
|
|||
project: Project
|
||||
}
|
||||
|
||||
function ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
export function BS3ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
const [isOpened, setIsOpened] = useState(false)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
|
@ -227,16 +237,16 @@ function ActionsDropdown({ project }: ActionDropdownProps) {
|
|||
}, [setIsOpened])
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
<BS3Dropdown
|
||||
id={`project-actions-dropdown-${project.id}`}
|
||||
pullRight
|
||||
open={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<Dropdown.Toggle noCaret className="btn-transparent">
|
||||
<BS3Dropdown.Toggle noCaret className="btn-transparent">
|
||||
<Icon type="ellipsis-h" fw />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="projects-dropdown-menu text-left">
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu className="projects-dropdown-menu text-left">
|
||||
<RenameProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<CopyProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<DownloadProjectButtonMenuItem
|
||||
|
@ -256,9 +266,181 @@ function ActionsDropdown({ project }: ActionDropdownProps) {
|
|||
<UntrashProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<LeaveProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<DeleteProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
</Dropdown.Menu>
|
||||
</BS3Dropdown.Menu>
|
||||
</BS3Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
function BS5ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Dropdown align="end">
|
||||
<DropdownToggle
|
||||
id={`project-actions-dropdown-toggle-btn-${project.id}`}
|
||||
bsPrefix="dropdown-table-button-toggle"
|
||||
>
|
||||
<MaterialIcon type="more_vert" accessibilityLabel={t('actions')} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>
|
||||
<RenameProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleOpenModal}
|
||||
leadingIcon="edit"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</RenameProjectButton>
|
||||
<CopyProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleOpenModal}
|
||||
leadingIcon="file_copy"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</CopyProjectButton>
|
||||
<DownloadProjectButton project={project}>
|
||||
{(text, downloadProject) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={downloadProject}
|
||||
leadingIcon="cloud_download"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</DownloadProjectButton>
|
||||
<CompileAndDownloadProjectPDFButton project={project}>
|
||||
{(text, pendingCompile, downloadProject) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
downloadProject()
|
||||
}}
|
||||
leadingIcon={
|
||||
pendingCompile ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
as="span"
|
||||
className="dropdown-item-leading-icon spinner"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
) : (
|
||||
'picture_as_pdf'
|
||||
)
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</CompileAndDownloadProjectPDFButton>
|
||||
<ArchiveProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleOpenModal}
|
||||
leadingIcon="inbox"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</ArchiveProjectButton>
|
||||
<TrashProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleOpenModal}
|
||||
leadingIcon="delete"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</TrashProjectButton>
|
||||
<UnarchiveProjectButton project={project}>
|
||||
{(text, unarchiveProject) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={unarchiveProject}
|
||||
leadingIcon="restore_page"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</UnarchiveProjectButton>
|
||||
<UntrashProjectButton project={project}>
|
||||
{(text, untrashProject) => (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={untrashProject}
|
||||
leadingIcon="restore_page"
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</UntrashProjectButton>
|
||||
<LeaveProjectButton project={project}>
|
||||
{text => (
|
||||
<li role="none">
|
||||
<DropdownItem as="button" tabIndex={-1} leadingIcon="logout">
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</LeaveProjectButton>
|
||||
<DeleteProjectButton project={project}>
|
||||
{text => (
|
||||
<li role="none">
|
||||
<DropdownItem as="button" tabIndex={-1} leadingIcon="block">
|
||||
{text}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</DeleteProjectButton>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
function ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<BS3ActionsDropdown project={project} />}
|
||||
bs5={<BS5ActionsDropdown project={project} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActionsDropdown
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ReactNode } from 'react'
|
|||
|
||||
type MenuItemButtonProps = {
|
||||
children: ReactNode
|
||||
onClick?: (...args: unknown[]) => void
|
||||
onClick?: (e?: React.MouseEvent) => void
|
||||
className?: string
|
||||
afterNode?: React.ReactNode
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectListContext } from '../context/project-list-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function LoadMore() {
|
||||
const {
|
||||
|
@ -13,16 +13,17 @@ export default function LoadMore() {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="text-centered">
|
||||
<div className="text-center">
|
||||
{hiddenProjectsCount > 0 ? (
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="project-list-load-more-button btn-secondary-info btn-secondary"
|
||||
onClick={() => loadMoreProjects()}
|
||||
aria-label={t('show_x_more_projects', { x: loadMoreCount })}
|
||||
>
|
||||
{t('show_x_more', { x: loadMoreCount })}
|
||||
</Button>
|
||||
<>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
className="project-list-load-more-button"
|
||||
onClick={() => loadMoreProjects()}
|
||||
>
|
||||
{t('show_x_more_projects', { x: loadMoreCount })}
|
||||
</OLButton>
|
||||
</>
|
||||
) : null}
|
||||
<p>
|
||||
{hiddenProjectsCount > 0 ? (
|
||||
|
@ -33,15 +34,13 @@ export default function LoadMore() {
|
|||
n: visibleProjects.length + hiddenProjectsCount,
|
||||
})}
|
||||
</span>{' '}
|
||||
<button
|
||||
type="button"
|
||||
<OLButton
|
||||
variant="link"
|
||||
onClick={() => showAllProjects()}
|
||||
style={{ padding: 0 }}
|
||||
className="btn-link"
|
||||
aria-label={t('show_all_projects')}
|
||||
className="btn-inline-link"
|
||||
>
|
||||
{t('show_all_uppercase')}
|
||||
</button>
|
||||
{t('show_all_projects')}
|
||||
</OLButton>
|
||||
</>
|
||||
) : (
|
||||
<span aria-live="polite">
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { memo, useEffect, useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
|
||||
|
@ -12,6 +11,7 @@ import OLModal, {
|
|||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
type ProjectsActionModalProps = {
|
||||
|
@ -80,7 +80,7 @@ function ProjectsActionModal({
|
|||
backdrop="static"
|
||||
>
|
||||
<OLModalHeader closeButton>
|
||||
<Modal.Title>{title}</Modal.Title>
|
||||
<OLModalTitle>{title}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
{children}
|
||||
|
|
|
@ -8,7 +8,7 @@ type ProjectsToDisplayProps = {
|
|||
|
||||
function ProjectsList({ projects, projectsToDisplay }: ProjectsToDisplayProps) {
|
||||
return (
|
||||
<ul className="projects-action-list">
|
||||
<ul>
|
||||
{projectsToDisplay.map(project => (
|
||||
<li
|
||||
key={`projects-action-list-${project.id}`}
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
ControlLabel,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
Modal,
|
||||
} from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import { renameProject } from '../../util/api'
|
||||
|
@ -17,6 +9,17 @@ import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
|
|||
import { debugConsole } from '@/utils/debugging'
|
||||
import { isSmallDevice } from '../../../../infrastructure/event-tracking'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLForm from '@/features/ui/components/ol/ol-form'
|
||||
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'
|
||||
|
||||
type RenameProjectModalProps = {
|
||||
handleCloseModal: () => void
|
||||
|
@ -82,24 +85,22 @@ function RenameProjectModal({
|
|||
]
|
||||
)
|
||||
|
||||
const handleOnChange = (
|
||||
event: React.ChangeEvent<HTMLFormElement & FormControl>
|
||||
) => {
|
||||
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewProjectName(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
animation
|
||||
show={showModal}
|
||||
onHide={handleCloseModal}
|
||||
id="rename-project-modal"
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('rename_project')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('rename_project')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
{isError && (
|
||||
<div className="notification-list">
|
||||
<Notification
|
||||
|
@ -108,41 +109,33 @@ function RenameProjectModal({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<form id="rename-project-form" onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor="rename-project-form-name">
|
||||
{t('new_name')}
|
||||
</ControlLabel>
|
||||
|
||||
<FormControl
|
||||
id="rename-project-form-name"
|
||||
<OLForm id="rename-project-form" onSubmit={handleSubmit}>
|
||||
<OLFormGroup controlId="rename-project-form-name">
|
||||
<OLFormLabel>{t('new_name')}</OLFormLabel>
|
||||
<OLFormControl
|
||||
type="text"
|
||||
placeholder={t('project_name')}
|
||||
required
|
||||
value={newProjectName}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
</OLFormGroup>
|
||||
</OLForm>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" onClick={handleCloseModal}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
form="rename-project-form"
|
||||
bsStyle="primary"
|
||||
disabled={isLoading || !isValid}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
form="rename-project-form"
|
||||
disabled={isLoading || !isValid}
|
||||
>
|
||||
{t('rename')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
} from '../context/project-list-context'
|
||||
import { ColorPickerProvider } from '../context/color-picker-context'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
|
||||
import CurrentPlanWidget from './current-plan-widget/current-plan-widget'
|
||||
|
@ -30,6 +29,9 @@ import OLCol from '@/features/ui/components/ol/ol-col'
|
|||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { TableContainer } from '@/features/ui/components/bootstrap-5/table'
|
||||
|
||||
function ProjectListRoot() {
|
||||
const { isReady } = useWaitForI18n()
|
||||
|
@ -75,6 +77,32 @@ function ProjectListPageContent() {
|
|||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tableTopArea = (
|
||||
<div
|
||||
className={classnames(
|
||||
'pt-2',
|
||||
'pb-3',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<div className="clearfix">
|
||||
<NewProjectButton
|
||||
id="new-project-button-projects-table"
|
||||
className="pull-left me-2"
|
||||
showAddAffiliationWidget
|
||||
/>
|
||||
<SearchForm
|
||||
inputValue={searchText}
|
||||
setInputValue={setSearchText}
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
className="overflow-hidden"
|
||||
formGroupProps={{ className: 'mb-0' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return isLoading ? (
|
||||
<div className="loading-container">
|
||||
<LoadingBranded loadProgress={loadProgress} label={t('loading')} />
|
||||
|
@ -94,41 +122,62 @@ function ProjectListPageContent() {
|
|||
<Sidebar />
|
||||
<div className="project-list-main-react">
|
||||
{error ? <DashApiError /> : ''}
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<OLRow>
|
||||
<OLCol>
|
||||
<UserNotifications />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<div className="project-list-header-row">
|
||||
<ProjectListTitle
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
selectedTagId={selectedTagId}
|
||||
className="hidden-xs text-truncate"
|
||||
className={classnames(
|
||||
'text-truncate',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
/>
|
||||
<div className="project-tools">
|
||||
<div className="hidden-xs">
|
||||
<div
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
{selectedProjects.length === 0 ? (
|
||||
<CurrentPlanWidget />
|
||||
) : (
|
||||
<ProjectTools />
|
||||
)}
|
||||
</div>
|
||||
<div className="visible-xs">
|
||||
<div
|
||||
className={bsVersion({
|
||||
bs5: 'd-md-none',
|
||||
bs3: 'visible-xs',
|
||||
})}
|
||||
>
|
||||
<CurrentPlanWidget />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Row className="hidden-xs">
|
||||
<Col md={7}>
|
||||
<OLRow
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
<OLCol lg={7}>
|
||||
<SearchForm
|
||||
inputValue={searchText}
|
||||
setInputValue={setSearchText}
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<div
|
||||
className={classnames(
|
||||
'project-list-sidebar-survey-wrapper',
|
||||
|
@ -137,60 +186,59 @@ function ProjectListPageContent() {
|
|||
>
|
||||
<SurveyWidget />
|
||||
</div>
|
||||
<div className="visible-xs mt-1">
|
||||
<div
|
||||
className={classnames(
|
||||
'mt-1',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<div role="toolbar" className="projects-toolbar">
|
||||
<ProjectsDropdown />
|
||||
<SortByDropdown />
|
||||
</div>
|
||||
</div>
|
||||
<Row className="row-spaced">
|
||||
<Col xs={12}>
|
||||
<div className="card project-list-card">
|
||||
<div className="visible-xs pt-2 pb-3">
|
||||
<div className="clearfix">
|
||||
<NewProjectButton
|
||||
id="new-project-button-projects-table"
|
||||
className="pull-left me-2"
|
||||
showAddAffiliationWidget
|
||||
/>
|
||||
<SearchForm
|
||||
inputValue={searchText}
|
||||
setInputValue={setSearchText}
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
className="overflow-hidden"
|
||||
formGroupProps={{ className: 'mb-0' }}
|
||||
/>
|
||||
<OLRow className="row-spaced">
|
||||
<OLCol>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<div className="card project-list-card">
|
||||
{tableTopArea}
|
||||
<ProjectListTable />
|
||||
</div>
|
||||
</div>
|
||||
<ProjectListTable />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="row-spaced">
|
||||
<Col xs={12}>
|
||||
}
|
||||
bs5={
|
||||
<TableContainer bordered>
|
||||
{tableTopArea}
|
||||
<ProjectListTable />
|
||||
</TableContainer>
|
||||
}
|
||||
/>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<OLRow className="row-spaced">
|
||||
<OLCol>
|
||||
<LoadMore />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="project-list-welcome-wrapper">
|
||||
{error ? <DashApiError /> : ''}
|
||||
<Row className="row-spaced mx-0">
|
||||
<OLRow className="row-spaced mx-0">
|
||||
<OLCol
|
||||
md={{ span: 10, offset: 1 }}
|
||||
lg={{ span: 8, offset: 2 }}
|
||||
className="project-list-empty-col"
|
||||
>
|
||||
<Row>
|
||||
<OLRow>
|
||||
<OLCol>
|
||||
<UserNotifications />
|
||||
</OLCol>
|
||||
</Row>
|
||||
</OLRow>
|
||||
<WelcomeMessage />
|
||||
</OLCol>
|
||||
</Row>
|
||||
</OLRow>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -201,7 +249,7 @@ function ProjectListPageContent() {
|
|||
function DashApiError() {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Row className="row-spaced">
|
||||
<OLRow className="row-spaced">
|
||||
<OLCol
|
||||
xs={{ span: 8, offset: 2 }}
|
||||
bs3Props={{ xs: 8, xsOffset: 2 }}
|
||||
|
@ -214,7 +262,7 @@ function DashApiError() {
|
|||
/>
|
||||
</div>
|
||||
</OLCol>
|
||||
</Row>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Sort } from '../../../../../../types/project/dashboard/api'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type SortBtnOwnProps = {
|
||||
column: string
|
||||
|
@ -26,7 +27,10 @@ function withContent<T extends SortBtnOwnProps>(
|
|||
let screenReaderText = t('sort_by_x', { x: text })
|
||||
|
||||
if (column === sort.by) {
|
||||
iconType = sort.order === 'asc' ? 'caret-up' : 'caret-down'
|
||||
iconType =
|
||||
sort.order === 'asc'
|
||||
? bsVersion({ bs5: 'arrow_upward_alt', bs3: 'caret-up' })
|
||||
: bsVersion({ bs5: 'arrow_downward_alt', bs3: 'caret-down' })
|
||||
screenReaderText = t('reverse_x_sort_order', { x: text })
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import ArchiveProjectModal from '../../../modals/archive-project-modal'
|
||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { archiveProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type ArchiveProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -65,20 +66,28 @@ const ArchiveProjectButtonTooltip = memo(function ArchiveProjectButtonTooltip({
|
|||
return (
|
||||
<ArchiveProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-archive-project-${project.id}`}
|
||||
id={`archive-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
<Icon type="inbox" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleOpenModal}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'inbox',
|
||||
bs3: 'inbox',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</ArchiveProjectButton>
|
||||
)
|
||||
|
|
|
@ -2,21 +2,31 @@ import { useTranslation } from 'react-i18next'
|
|||
import { memo, useCallback, useState } from 'react'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||
import { useLocation } from '../../../../../../shared/hooks/use-location'
|
||||
import useAbortController from '../../../../../../shared/hooks/use-abort-controller'
|
||||
import { postJSON } from '../../../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../../../shared/components/accessible-modal'
|
||||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
|
||||
type CompileAndDownloadProjectPDFButtonProps = {
|
||||
project: Project
|
||||
children: (
|
||||
text: string,
|
||||
pendingDownload: boolean,
|
||||
downloadProject: (fn: () => void) => void
|
||||
downloadProject: <T extends React.MouseEvent>(
|
||||
e?: T,
|
||||
fn?: (e?: T) => void
|
||||
) => void
|
||||
) => React.ReactElement
|
||||
}
|
||||
|
||||
|
@ -31,7 +41,7 @@ function CompileAndDownloadProjectPDFButton({
|
|||
const [pendingCompile, setPendingCompile] = useState(false)
|
||||
|
||||
const downloadProject = useCallback(
|
||||
onDone => {
|
||||
<T extends React.MouseEvent>(e?: T, onDone?: (e?: T) => void) => {
|
||||
setPendingCompile(pendingCompile => {
|
||||
if (pendingCompile) return true
|
||||
eventTracking.sendMB('project-list-page-interaction', {
|
||||
|
@ -72,7 +82,7 @@ function CompileAndDownloadProjectPDFButton({
|
|||
location.assign(
|
||||
`/download/project/${project.id}/build/${outputFile.build}/output/output.pdf?${params}`
|
||||
)
|
||||
onDone()
|
||||
onDone?.(e)
|
||||
} else {
|
||||
setShowErrorModal(true)
|
||||
}
|
||||
|
@ -111,19 +121,19 @@ function CompileErrorModal({
|
|||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
<AccessibleModal show onHide={handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<OLModal show onHide={handleClose}>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>
|
||||
{project.name}: {t('pdf_unavailable_for_download')}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{t('generic_linked_file_compile_error')}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<a href={`/project/${project.id}`}>
|
||||
<Button bsStyle="primary">{t('open_project')}</Button>
|
||||
</a>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>{t('generic_linked_file_compile_error')}</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="primary" href={`/project/${project.id}`}>
|
||||
{t('open_project')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -135,24 +145,35 @@ const CompileAndDownloadProjectPDFButtonTooltip = memo(
|
|||
return (
|
||||
<CompileAndDownloadProjectPDFButton project={project}>
|
||||
{(text, pendingCompile, compileAndDownloadProject) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-compile-and-download-project-${project.id}`}
|
||||
id={`compile-and-download-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={() => compileAndDownloadProject(() => {})}
|
||||
>
|
||||
{pendingCompile ? (
|
||||
<Icon type="spinner" spin />
|
||||
) : (
|
||||
<Icon type="file-pdf-o" />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={compileAndDownloadProject}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
loadingLabel={text}
|
||||
isLoading={pendingCompile}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'picture_as_pdf',
|
||||
bs3: 'file-pdf-o',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{
|
||||
fw: true,
|
||||
loading: pendingCompile ? (
|
||||
<Icon type="spinner" fw accessibilityLabel={text} spin />
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</CompileAndDownloadProjectPDFButton>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { memo, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import CloneProjectModal from '../../../../../clone-project-modal/components/clone-project-modal'
|
||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
|
@ -12,14 +10,18 @@ import {
|
|||
} from '../../../../../../../../types/project/dashboard/api'
|
||||
import { useProjectTags } from '@/features/project-list/hooks/use-project-tags'
|
||||
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
|
||||
|
||||
type HandleOpenModal = (fn?: () => void) => void
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type CopyButtonProps = {
|
||||
project: Project
|
||||
children: (
|
||||
text: string,
|
||||
handleOpenModal: HandleOpenModal
|
||||
handleOpenModal: <T extends React.MouseEvent>(
|
||||
e?: T,
|
||||
fn?: (e?: T) => void
|
||||
) => void
|
||||
) => React.ReactElement
|
||||
}
|
||||
|
||||
|
@ -37,9 +39,9 @@ function CopyProjectButton({ project, children }: CopyButtonProps) {
|
|||
const projectTags = useProjectTags(project.id)
|
||||
|
||||
const handleOpenModal = useCallback(
|
||||
(onOpen?: Parameters<HandleOpenModal>[0]) => {
|
||||
<T extends React.MouseEvent>(e?: T, onOpen?: (e?: T) => void) => {
|
||||
setShowModal(true)
|
||||
onOpen?.()
|
||||
onOpen?.(e)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
@ -97,20 +99,28 @@ const CopyProjectButtonTooltip = memo(function CopyProjectButtonTooltip({
|
|||
return (
|
||||
<CopyProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-copy-project-${project.id}`}
|
||||
id={`copy-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={() => handleOpenModal()}
|
||||
>
|
||||
<Icon type="files-o" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleOpenModal}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'file_copy',
|
||||
bs3: 'files-o',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</CopyProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import DeleteProjectModal from '../../../modals/delete-project-modal'
|
||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { deleteProject } from '../../../../util/api'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type DeleteProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -63,20 +64,28 @@ const DeleteProjectButtonTooltip = memo(function DeleteProjectButtonTooltip({
|
|||
return (
|
||||
<DeleteProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-delete-project-${project.id}`}
|
||||
id={`delete-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
<Icon type="ban" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleOpenModal}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'block',
|
||||
bs3: 'ban',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</DeleteProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||
import { useLocation } from '../../../../../../shared/hooks/use-location'
|
||||
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type DownloadProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -39,20 +40,28 @@ const DownloadProjectButtonTooltip = memo(
|
|||
return (
|
||||
<DownloadProjectButton project={project}>
|
||||
{(text, downloadProject) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-download-project-${project.id}`}
|
||||
id={`download-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={downloadProject}
|
||||
>
|
||||
<Icon type="cloud-download" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={downloadProject}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'cloud_download',
|
||||
bs3: 'cloud-download',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</DownloadProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import LeaveProjectModal from '../../../modals/leave-project-modal'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { leaveProject } from '../../../../util/api'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type LeaveProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -62,20 +63,28 @@ const LeaveProjectButtonTooltip = memo(function LeaveProjectButtonTooltip({
|
|||
return (
|
||||
<LeaveProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-leave-project-${project.id}`}
|
||||
id={`leave-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
<Icon type="sign-out" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleOpenModal}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'logout',
|
||||
bs3: 'sign-out',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</LeaveProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import TrashProjectModal from '../../../modals/trash-project-modal'
|
||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { trashProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type TrashProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -62,20 +63,28 @@ const TrashProjectButtonTooltip = memo(function TrashProjectButtonTooltip({
|
|||
return (
|
||||
<TrashProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-trash-project-${project.id}`}
|
||||
id={`trash-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
<Icon type="trash" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleOpenModal}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'delete',
|
||||
bs3: 'trash',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</TrashProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { unarchiveProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type UnarchiveProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -41,20 +42,28 @@ const UnarchiveProjectButtonTooltip = memo(
|
|||
return (
|
||||
<UnarchiveProjectButton project={project}>
|
||||
{(text, handleUnarchiveProject) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-unarchive-project-${project.id}`}
|
||||
id={`unarchive-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={handleUnarchiveProject}
|
||||
>
|
||||
<Icon type="reply" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleUnarchiveProject}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'restore_page',
|
||||
bs3: 'reply',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</UnarchiveProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { untrashProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type UntrashProjectButtonProps = {
|
||||
project: Project
|
||||
|
@ -40,20 +41,28 @@ const UntrashProjectButtonTooltip = memo(function UntrashProjectButtonTooltip({
|
|||
return (
|
||||
<UntrashProjectButton project={project}>
|
||||
{(text, handleUntrashProject) => (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-untrash-project-${project.id}`}
|
||||
id={`untrash-project-${project.id}`}
|
||||
description={text}
|
||||
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
<button
|
||||
className="btn btn-link action-btn"
|
||||
aria-label={text}
|
||||
onClick={handleUntrashProject}
|
||||
>
|
||||
<Icon type="reply" fw />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
<OLIconButton
|
||||
onClick={handleUntrashProject}
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={
|
||||
bsVersion({
|
||||
bs5: 'restore_page',
|
||||
bs3: 'reply',
|
||||
}) as string
|
||||
}
|
||||
bs3Props={{ fw: true }}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
</UntrashProjectButton>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { formatDate, fromNowDate } from '../../../../../utils/dates'
|
||||
import { Project } from '../../../../../../../types/project/dashboard/api'
|
||||
import Tooltip from '../../../../../shared/components/tooltip'
|
||||
import { LastUpdatedBy } from '@/features/project-list/components/table/cells/last-updated-by'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
|
||||
type LastUpdatedCellProps = {
|
||||
project: Project
|
||||
|
@ -12,7 +12,7 @@ export default function LastUpdatedCell({ project }: LastUpdatedCellProps) {
|
|||
|
||||
const tooltipText = formatDate(project.lastUpdated)
|
||||
return (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-last-updated-${project.id}`}
|
||||
id={`tooltip-last-updated-${project.id}`}
|
||||
description={tooltipText}
|
||||
|
@ -28,6 +28,6 @@ export default function LastUpdatedCell({ project }: LastUpdatedCellProps) {
|
|||
) : (
|
||||
<span>{lastUpdatedDate}</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../shared/components/tooltip'
|
||||
import { getOwnerName } from '../../../util/project'
|
||||
import { Project } from '../../../../../../../types/project/dashboard/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
|
||||
type LinkSharingIconProps = {
|
||||
prependSpace: boolean
|
||||
|
@ -17,7 +17,7 @@ function LinkSharingIcon({
|
|||
}: LinkSharingIconProps) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key={`tooltip-link-sharing-${project.id}`}
|
||||
id={`tooltip-link-sharing-${project.id}`}
|
||||
description={t('link_sharing')}
|
||||
|
@ -32,7 +32,7 @@ function LinkSharingIcon({
|
|||
accessibilityLabel={t('link_sharing')}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectListContext } from '@/features/project-list/context/project-list-context'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
|
||||
export const ProjectCheckbox = memo<{ projectId: string }>(({ projectId }) => {
|
||||
const { selectedProjectIds, toggleSelectedProject } = useProjectListContext()
|
||||
export const ProjectCheckbox = memo<{ projectId: string; projectName: string }>(
|
||||
({ projectId, projectName }) => {
|
||||
const { t } = useTranslation()
|
||||
const { selectedProjectIds, toggleSelectedProject } =
|
||||
useProjectListContext()
|
||||
|
||||
const handleCheckboxChange = useCallback(
|
||||
event => {
|
||||
toggleSelectedProject(projectId, event.target.checked)
|
||||
},
|
||||
[projectId, toggleSelectedProject]
|
||||
)
|
||||
const handleCheckboxChange = useCallback(
|
||||
event => {
|
||||
toggleSelectedProject(projectId, event.target.checked)
|
||||
},
|
||||
[projectId, toggleSelectedProject]
|
||||
)
|
||||
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`select-project-${projectId}`}
|
||||
autoComplete="off"
|
||||
checked={selectedProjectIds.has(projectId)}
|
||||
onChange={handleCheckboxChange}
|
||||
data-project-id={projectId}
|
||||
/>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
onChange={handleCheckboxChange}
|
||||
checked={selectedProjectIds.has(projectId)}
|
||||
aria-label={t('select_project', { project: projectName })}
|
||||
data-project-id={projectId}
|
||||
bs3Props={{ bsClass: 'dash-cell-checkbox-wrapper' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ProjectCheckbox.displayName = 'ProjectCheckbox'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import InlineTags from './cells/inline-tags'
|
||||
import OwnerCell from './cells/owner-cell'
|
||||
import LastUpdatedCell from './cells/last-updated-cell'
|
||||
|
@ -9,45 +8,90 @@ import { getOwnerName } from '../../util/project'
|
|||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import { ProjectCheckbox } from './project-checkbox'
|
||||
import { ProjectListOwnerName } from '@/features/project-list/components/table/project-list-owner-name'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type ProjectListTableRowProps = {
|
||||
project: Project
|
||||
selected: boolean
|
||||
}
|
||||
function ProjectListTableRow({ project }: ProjectListTableRowProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
function ProjectListTableRow({ project, selected }: ProjectListTableRowProps) {
|
||||
const ownerName = getOwnerName(project)
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="dash-cell-checkbox hidden-xs">
|
||||
<ProjectCheckbox projectId={project.id} />
|
||||
<label htmlFor={`select-project-${project.id}`} className="sr-only">
|
||||
{t('select_project', { project: project.name })}
|
||||
</label>
|
||||
<tr className={selected ? bsVersion({ bs5: 'table-active' }) : undefined}>
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-checkbox',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
>
|
||||
<ProjectCheckbox projectId={project.id} projectName={project.name} />
|
||||
</td>
|
||||
<td className="dash-cell-name">
|
||||
<a href={`/project/${project.id}`}>{project.name}</a>{' '}
|
||||
<InlineTags className="hidden-xs" projectId={project.id} />
|
||||
<InlineTags
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-inline',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
projectId={project.id}
|
||||
/>
|
||||
</td>
|
||||
<td className="dash-cell-date-owner visible-xs pb-0">
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-date-owner',
|
||||
'pb-0',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<LastUpdatedCell project={project} />
|
||||
{ownerName ? <ProjectListOwnerName ownerName={ownerName} /> : null}
|
||||
</td>
|
||||
<td className="dash-cell-owner hidden-xs">
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-owner',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
>
|
||||
<OwnerCell project={project} />
|
||||
</td>
|
||||
<td className="dash-cell-date hidden-xs">
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-date',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
>
|
||||
<LastUpdatedCell project={project} />
|
||||
</td>
|
||||
<td className="dash-cell-tag visible-xs pt-0">
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-tag',
|
||||
'pt-0',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<InlineTags projectId={project.id} />
|
||||
</td>
|
||||
<td className="dash-cell-actions">
|
||||
<div className="hidden-xs">
|
||||
<div
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
<ActionsCell project={project} />
|
||||
</div>
|
||||
<div className="visible-xs">
|
||||
<div className={bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })}>
|
||||
<ActionsDropdown project={project} />
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -1,21 +1,37 @@
|
|||
import { useCallback } from 'react'
|
||||
import { useCallback, useRef, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import ProjectListTableRow from './project-list-table-row'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import useSort from '../../hooks/use-sort'
|
||||
import withContent, { SortBtnProps } from '../sort/with-content'
|
||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import OLTable from '@/features/ui/components/ol/ol-table'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
function SortBtn({ onClick, text, iconType, screenReaderText }: SortBtnProps) {
|
||||
return (
|
||||
<button
|
||||
className="btn-link table-header-sort-btn hidden-xs"
|
||||
className={classnames(
|
||||
'table-header-sort-btn',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-inline-block',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
onClick={onClick}
|
||||
aria-label={screenReaderText}
|
||||
>
|
||||
<span className="tablesort-text">{text}</span>
|
||||
{iconType && <Icon type={iconType} />}
|
||||
<span className={bsVersion({ bs3: 'tablesort-text' })}>{text}</span>
|
||||
{iconType && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type={iconType} />}
|
||||
bs5={<MaterialIcon type={iconType} />}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -31,6 +47,7 @@ function ProjectListTable() {
|
|||
selectOrUnselectAllProjects,
|
||||
} = useProjectListContext()
|
||||
const { handleSort } = useSort()
|
||||
const checkAllRef = useRef<HTMLInputElement>()
|
||||
|
||||
const handleAllProjectsCheckboxChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -39,18 +56,39 @@ function ProjectListTable() {
|
|||
[selectOrUnselectAllProjects]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (checkAllRef.current) {
|
||||
checkAllRef.current.indeterminate =
|
||||
selectedProjects.length > 0 &&
|
||||
selectedProjects.length !== visibleProjects.length
|
||||
}
|
||||
}, [selectedProjects, visibleProjects])
|
||||
|
||||
return (
|
||||
<table className="project-dash-table">
|
||||
<caption className="sr-only">{t('projects_list')}</caption>
|
||||
<thead className="sr-only-xs">
|
||||
<OLTable className="project-dash-table" container={false} hover>
|
||||
<caption
|
||||
className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}
|
||||
>
|
||||
{t('projects_list')}
|
||||
</caption>
|
||||
<thead
|
||||
className={bsVersion({
|
||||
bs5: 'visually-hidden-max-md',
|
||||
bs3: 'sr-only-xs',
|
||||
})}
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
className="dash-cell-checkbox hidden-xs"
|
||||
className={classnames(
|
||||
'dash-cell-checkbox',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
aria-label={t('select_projects')}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="project-list-table-select-all"
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
onChange={handleAllProjectsCheckboxChange}
|
||||
checked={
|
||||
|
@ -58,10 +96,13 @@ function ProjectListTable() {
|
|||
visibleProjects.length !== 0
|
||||
}
|
||||
disabled={visibleProjects.length === 0}
|
||||
aria-label={t('select_all_projects')}
|
||||
bs3Props={{
|
||||
bsClass: 'dash-cell-checkbox-wrapper',
|
||||
inputRef: undefined,
|
||||
}}
|
||||
inputRef={checkAllRef}
|
||||
/>
|
||||
<label htmlFor="project-list-table-select-all" className="sr-only">
|
||||
{t('select_all_projects')}
|
||||
</label>
|
||||
</th>
|
||||
<th
|
||||
className="dash-cell-name"
|
||||
|
@ -82,13 +123,22 @@ function ProjectListTable() {
|
|||
/>
|
||||
</th>
|
||||
<th
|
||||
className="dash-cell-date-owner visible-xs"
|
||||
className={classnames(
|
||||
'dash-cell-date-owner',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
aria-label={t('date_and_owner')}
|
||||
>
|
||||
{t('date_and_owner')}
|
||||
</th>
|
||||
<th
|
||||
className="dash-cell-owner hidden-xs"
|
||||
className={classnames(
|
||||
'dash-cell-owner',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
aria-label={t('owner')}
|
||||
aria-sort={
|
||||
sort.by === 'owner'
|
||||
|
@ -106,7 +156,13 @@ function ProjectListTable() {
|
|||
/>
|
||||
</th>
|
||||
<th
|
||||
className="dash-cell-date hidden-xs"
|
||||
className={classnames(
|
||||
'dash-cell-date',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
aria-label={t('last_modified')}
|
||||
aria-sort={
|
||||
sort.by === 'lastUpdated'
|
||||
|
@ -123,7 +179,13 @@ function ProjectListTable() {
|
|||
onClick={() => handleSort('lastUpdated')}
|
||||
/>
|
||||
</th>
|
||||
<th className="dash-cell-tag visible-xs" aria-label={t('tags')}>
|
||||
<th
|
||||
className={classnames(
|
||||
'dash-cell-tag',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
aria-label={t('tags')}
|
||||
>
|
||||
{t('tags')}
|
||||
</th>
|
||||
<th className="dash-cell-actions" aria-label={t('actions')}>
|
||||
|
@ -131,21 +193,24 @@ function ProjectListTable() {
|
|||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{visibleProjects.length ? (
|
||||
visibleProjects.map((p: Project) => (
|
||||
<ProjectListTableRow project={p} key={p.id} />
|
||||
{visibleProjects.length > 0 ? (
|
||||
visibleProjects.map(p => (
|
||||
<ProjectListTableRow
|
||||
project={p}
|
||||
selected={selectedProjects.some(({ id }) => id === p.id)}
|
||||
key={p.id}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<tr className="no-projects">
|
||||
<td className="project-list-table-no-projects-cell" colSpan={5}>
|
||||
<td className="text-center" colSpan={5}>
|
||||
{t('no_projects')}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</OLTable>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ export default function Button({
|
|||
className,
|
||||
leadingIcon,
|
||||
isLoading = false,
|
||||
loadingLabel,
|
||||
size = 'default',
|
||||
trailingIcon,
|
||||
variant = 'primary',
|
||||
|
@ -41,7 +42,9 @@ export default function Button({
|
|||
className={loadingSpinnerClassName}
|
||||
role="status"
|
||||
/>
|
||||
<span className="visually-hidden">{t('loading')}</span>
|
||||
<span className="visually-hidden">
|
||||
{loadingLabel ?? t('loading')}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<span className="button-content" aria-hidden={isLoading}>
|
||||
|
|
|
@ -29,7 +29,44 @@ export const DropdownItem = forwardRef<
|
|||
{ active, children, description, leadingIcon, trailingIcon, ...props },
|
||||
ref
|
||||
) => {
|
||||
const trailingIconType = active ? 'check' : trailingIcon
|
||||
let leadingIconComponent = null
|
||||
if (leadingIcon) {
|
||||
if (typeof leadingIcon === 'string') {
|
||||
leadingIconComponent = (
|
||||
<MaterialIcon
|
||||
className="dropdown-item-leading-icon"
|
||||
type={leadingIcon}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
leadingIconComponent = (
|
||||
<span className="dropdown-item-leading-icon" aria-hidden="true">
|
||||
{leadingIcon}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let trailingIconComponent = null
|
||||
if (trailingIcon) {
|
||||
if (typeof trailingIcon === 'string') {
|
||||
const trailingIconType = active ? 'check' : trailingIcon
|
||||
|
||||
trailingIconComponent = (
|
||||
<MaterialIcon
|
||||
className="dropdown-item-trailing-icon"
|
||||
type={trailingIconType}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
trailingIconComponent = (
|
||||
<span className="dropdown-item-leading-icon" aria-hidden="true">
|
||||
{trailingIcon}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<BS5DropdownItem
|
||||
active={active}
|
||||
|
@ -38,19 +75,9 @@ export const DropdownItem = forwardRef<
|
|||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
{leadingIcon && (
|
||||
<MaterialIcon
|
||||
className="dropdown-item-leading-icon"
|
||||
type={leadingIcon}
|
||||
/>
|
||||
)}
|
||||
{leadingIconComponent}
|
||||
{children}
|
||||
{trailingIconType && (
|
||||
<MaterialIcon
|
||||
className="dropdown-item-trailing-icon"
|
||||
type={trailingIconType}
|
||||
/>
|
||||
)}
|
||||
{trailingIconComponent}
|
||||
{description && (
|
||||
<span className="dropdown-item-description">{description}</span>
|
||||
)}
|
||||
|
|
|
@ -1,22 +1,40 @@
|
|||
import { Table as BS5Table } from 'react-bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
function Table({ responsive, ...rest }: React.ComponentProps<typeof BS5Table>) {
|
||||
const content = (
|
||||
export function TableContainer({
|
||||
responsive,
|
||||
bordered,
|
||||
children,
|
||||
}: React.ComponentProps<typeof BS5Table>) {
|
||||
return (
|
||||
<div
|
||||
className={classnames('table-container', {
|
||||
'table-container-bordered': rest.bordered,
|
||||
'table-container-bordered': bordered,
|
||||
'table-responsive': responsive,
|
||||
})}
|
||||
>
|
||||
<BS5Table {...rest} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (responsive) {
|
||||
return <div className="table-responsive d-flex">{content}</div>
|
||||
}
|
||||
type TableProps = React.ComponentProps<typeof BS5Table> & {
|
||||
container?: boolean
|
||||
}
|
||||
|
||||
return content
|
||||
function Table({
|
||||
container = true,
|
||||
responsive,
|
||||
bordered,
|
||||
...rest
|
||||
}: TableProps) {
|
||||
return container ? (
|
||||
<TableContainer responsive={responsive} bordered={bordered}>
|
||||
<BS5Table {...rest} />
|
||||
</TableContainer>
|
||||
) : (
|
||||
<BS5Table {...rest} />
|
||||
)
|
||||
}
|
||||
|
||||
export default Table
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { Form } from 'react-bootstrap-5'
|
||||
import { Checkbox as BS3Checkbox } from 'react-bootstrap'
|
||||
import { Checkbox as BS3Checkbox, Radio as BS3Radio } from 'react-bootstrap'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type OLFormCheckboxProps = React.ComponentProps<(typeof Form)['Check']> & {
|
||||
inputRef?: React.MutableRefObject<HTMLInputElement | undefined>
|
||||
bs3Props?: Record<string, unknown>
|
||||
}
|
||||
|
||||
function OLFormCheckbox(props: OLFormCheckboxProps) {
|
||||
const { bs3Props, ...rest } = props
|
||||
const { bs3Props, inputRef, ...rest } = props
|
||||
|
||||
const bs3FormLabelProps: React.ComponentProps<typeof BS3Checkbox> = {
|
||||
children: rest.label,
|
||||
|
@ -17,14 +19,27 @@ function OLFormCheckbox(props: OLFormCheckboxProps) {
|
|||
disabled: rest.disabled,
|
||||
inline: rest.inline,
|
||||
title: rest.title,
|
||||
autoComplete: rest.autoComplete,
|
||||
onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
|
||||
inputRef: node => {
|
||||
if (inputRef) {
|
||||
inputRef.current = node
|
||||
}
|
||||
},
|
||||
...getAriaAndDataProps(rest),
|
||||
...bs3Props,
|
||||
}
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<BS3Checkbox {...bs3FormLabelProps} />}
|
||||
bs5={<Form.Check {...rest} />}
|
||||
bs3={
|
||||
rest.type === 'radio' ? (
|
||||
<BS3Radio {...bs3FormLabelProps} />
|
||||
) : (
|
||||
<BS3Checkbox {...bs3FormLabelProps} />
|
||||
)
|
||||
}
|
||||
bs5={<Form.Check ref={inputRef} {...rest} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { forwardRef } from 'react'
|
|||
import { Form } from 'react-bootstrap-5'
|
||||
import { FormControl as BS3FormControl } from 'react-bootstrap'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type OLFormControlProps = React.ComponentProps<(typeof Form)['Control']> & {
|
||||
bs3Props?: Record<string, unknown>
|
||||
|
@ -22,6 +23,7 @@ const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
|
|||
placeholder: rest.placeholder,
|
||||
readOnly: rest.readOnly,
|
||||
autoComplete: rest.autoComplete,
|
||||
autoFocus: rest.autoFocus,
|
||||
minLength: rest.minLength,
|
||||
maxLength: rest.maxLength,
|
||||
onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
|
||||
|
@ -38,20 +40,9 @@ const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
|
|||
...bs3Props,
|
||||
}
|
||||
|
||||
// get all `aria-*` and `data-*` attributes
|
||||
const extraProps = Object.entries(rest).reduce(
|
||||
(acc, [key, value]) => {
|
||||
if (key.startsWith('aria-') || key.startsWith('data-')) {
|
||||
acc[key] = value
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, string>
|
||||
)
|
||||
|
||||
bs3FormControlProps = {
|
||||
...bs3FormControlProps,
|
||||
...extraProps,
|
||||
...getAriaAndDataProps(rest),
|
||||
'data-ol-dirty': rest['data-ol-dirty'],
|
||||
} as typeof bs3FormControlProps & Record<string, unknown>
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ function OLForm(props: OLFormProps) {
|
|||
componentClass: rest.as,
|
||||
bsClass: rest.className,
|
||||
children: rest.children,
|
||||
id: rest.id,
|
||||
onSubmit: rest.onSubmit as React.FormEventHandler<BS3Form> | undefined,
|
||||
...bs3Props,
|
||||
}
|
||||
|
|
|
@ -17,18 +17,19 @@ export type OLIconButtonProps = IconButtonProps & {
|
|||
export default function OLIconButton(props: OLIconButtonProps) {
|
||||
const { bs3Props, ...rest } = props
|
||||
|
||||
const { fw, ...bs3Rest } = bs3Props || {}
|
||||
const { fw, loading, ...bs3Rest } = bs3Props || {}
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<BS3Button {...bs3ButtonProps(rest)} {...bs3Rest}>
|
||||
{bs3Props?.loading}
|
||||
<Icon
|
||||
type={rest.icon}
|
||||
fw={fw}
|
||||
accessibilityLabel={rest.accessibilityLabel}
|
||||
/>
|
||||
{loading || (
|
||||
<Icon
|
||||
type={rest.icon}
|
||||
fw={fw}
|
||||
accessibilityLabel={rest.accessibilityLabel}
|
||||
/>
|
||||
)}
|
||||
</BS3Button>
|
||||
}
|
||||
bs5={<IconButton {...rest} />}
|
||||
|
|
|
@ -7,7 +7,7 @@ type OLFormProps = React.ComponentProps<typeof Table> & {
|
|||
}
|
||||
|
||||
function OLTable(props: OLFormProps) {
|
||||
const { bs3Props, ...rest } = props
|
||||
const { bs3Props, container, ...rest } = props
|
||||
|
||||
const bs3FormProps: React.ComponentProps<typeof BS3Table> = {
|
||||
bsClass: rest.className,
|
||||
|
@ -21,7 +21,7 @@ function OLTable(props: OLFormProps) {
|
|||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<BS3Table {...bs3FormProps} />}
|
||||
bs5={<Table {...rest} />}
|
||||
bs5={<Table container={container} {...rest} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@ function OLTooltip(props: OLTooltipProps) {
|
|||
children: bs5Props.children,
|
||||
id: bs5Props.id,
|
||||
description: bs5Props.description,
|
||||
overlayProps: {},
|
||||
overlayProps: {
|
||||
placement: bs5Props.overlayProps?.placement,
|
||||
},
|
||||
...bs3Props,
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ export type ButtonProps = {
|
|||
target?: string
|
||||
rel?: string
|
||||
isLoading?: boolean
|
||||
loadingLabel?: string
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>
|
||||
size?: 'small' | 'default' | 'large'
|
||||
trailingIcon?: string
|
||||
|
|
|
@ -24,9 +24,9 @@ export type DropdownItemProps = PropsWithChildren<{
|
|||
disabled?: boolean
|
||||
eventKey?: string | number
|
||||
href?: string
|
||||
leadingIcon?: string
|
||||
leadingIcon?: string | React.ReactNode
|
||||
onClick?: React.MouseEventHandler
|
||||
trailingIcon?: string
|
||||
trailingIcon?: string | React.ReactNode
|
||||
variant?: 'default' | 'danger'
|
||||
className?: string
|
||||
role?: string
|
||||
|
|
|
@ -5,3 +5,16 @@ export const isBootstrap5 = getMeta('ol-bootstrapVersion') === 5
|
|||
export const bsVersion = ({ bs5, bs3 }: { bs5?: string; bs3?: string }) => {
|
||||
return isBootstrap5 ? bs5 : bs3
|
||||
}
|
||||
|
||||
// get all `aria-*` and `data-*` attributes
|
||||
export const getAriaAndDataProps = (obj: Record<string, unknown>) => {
|
||||
return Object.entries(obj).reduce(
|
||||
(acc, [key, value]) => {
|
||||
if (key.startsWith('aria-') || key.startsWith('data-')) {
|
||||
acc[key] = value
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type IconProps = React.ComponentProps<'i'> & {
|
||||
type: string
|
||||
|
@ -20,7 +21,9 @@ function MaterialIcon({
|
|||
{type}
|
||||
</span>
|
||||
{accessibilityLabel && (
|
||||
<span className="sr-only">{accessibilityLabel}</span>
|
||||
<span className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}>
|
||||
{accessibilityLabel}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -417,11 +417,13 @@
|
|||
border: 0;
|
||||
text-align: left;
|
||||
color: @ol-type-color;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
|
@ -437,6 +439,14 @@
|
|||
input[type='checkbox'] {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.dash-cell-checkbox-wrapper {
|
||||
label {
|
||||
display: block;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
|
@ -827,13 +837,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.project-list-load-more {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.project-list-load-more-button {
|
||||
margin-bottom: @margin-sm;
|
||||
}
|
||||
|
|
|
@ -121,16 +121,11 @@
|
|||
.project-list-table-name-cell,
|
||||
.project-list-table-owner-cell,
|
||||
.project-list-table-lastupdated-cell,
|
||||
.project-list-table-actions-cell,
|
||||
.project-list-table-no-projects-cell {
|
||||
.project-list-table-actions-cell {
|
||||
padding: (@line-height-computed / 4) 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.project-list-table-no-projects-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.project-list-table-name-cell {
|
||||
width: 50%;
|
||||
padding-right: @line-height-computed / 2;
|
||||
|
|
|
@ -52,5 +52,8 @@
|
|||
// Components custom style
|
||||
@import '../components/all';
|
||||
|
||||
// Custom helpers
|
||||
@import '../helpers/all';
|
||||
|
||||
// Pages custom style
|
||||
@import '../pages/all';
|
||||
|
|
|
@ -63,3 +63,7 @@ pre,
|
|||
samp {
|
||||
@include body-base;
|
||||
}
|
||||
|
||||
.list-style-check-green {
|
||||
list-style-image: url('../../../../public/img/fa-check-green.svg');
|
||||
}
|
||||
|
|
|
@ -186,6 +186,10 @@
|
|||
.icon-large {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: var(--spacing-01);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button-small {
|
||||
|
|
|
@ -92,6 +92,11 @@
|
|||
|
||||
.dropdown-item-leading-icon {
|
||||
padding-right: var(--spacing-04);
|
||||
|
||||
&.spinner {
|
||||
margin-left: var(--spacing-01);
|
||||
margin-right: var(--spacing-01);
|
||||
}
|
||||
}
|
||||
|
||||
// description text should look disabled when the dropdown item is disabled
|
||||
|
|
|
@ -1,74 +1,50 @@
|
|||
.table-container {
|
||||
flex: 1;
|
||||
margin-bottom: var(--spacing-06);
|
||||
background-color: var(--white);
|
||||
|
||||
.table {
|
||||
margin-bottom: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container-bordered {
|
||||
--table-container-border-width: var(--bs-border-width);
|
||||
|
||||
border-color: $table-border-color;
|
||||
border-radius: var(--border-radius-base);
|
||||
border-width: var(--table-container-border-width);
|
||||
border-style: solid;
|
||||
|
||||
.table {
|
||||
th,
|
||||
td {
|
||||
&:first-child {
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
border-top-width: 0;
|
||||
|
||||
th,
|
||||
.table {
|
||||
tr {
|
||||
&:last-child {
|
||||
td {
|
||||
&:first-child {
|
||||
border-top-left-radius: calc(
|
||||
var(--border-radius-base) - var(--table-container-border-width)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
&:last-child {
|
||||
border-top-right-radius: calc(
|
||||
var(--border-radius-base) - var(--table-container-border-width)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-bottom-width: 0;
|
||||
|
||||
th,
|
||||
td {
|
||||
&:first-child {
|
||||
border-bottom-left-radius: calc(
|
||||
var(--border-radius-base) - var(--table-container-border-width)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
&:last-child {
|
||||
border-bottom-right-radius: calc(
|
||||
var(--border-radius-base) - var(--table-container-border-width)
|
||||
);
|
||||
}
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-container-bordered {
|
||||
padding: var(--spacing-04);
|
||||
border-color: $table-border-color;
|
||||
border-radius: var(--border-radius-base);
|
||||
border-width: var(--bs-border-width);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.table-hover {
|
||||
th {
|
||||
&:hover {
|
||||
background-color: $table-hover-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-striped {
|
||||
tr,
|
||||
td {
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@import 'visually-hidden';
|
|
@ -0,0 +1,5 @@
|
|||
.visually-hidden-max-md {
|
||||
@include media-breakpoint-down(md) {
|
||||
@include visually-hidden();
|
||||
}
|
||||
}
|
|
@ -12,6 +12,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0 var(--spacing-02);
|
||||
}
|
||||
|
||||
.project-list-react {
|
||||
body > &.content {
|
||||
padding-top: $header-height;
|
||||
|
@ -155,6 +159,240 @@
|
|||
overflow-x: hidden;
|
||||
padding: var(--spacing-08) var(--spacing-06);
|
||||
}
|
||||
|
||||
.project-dash-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
tr:not(:last-child) {
|
||||
border-bottom: 1px solid $table-border-color;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr.no-projects:hover {
|
||||
td {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-header-sort-btn {
|
||||
border: 0;
|
||||
text-align: left;
|
||||
color: var(--content-secondary);
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--content-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.material-symbols {
|
||||
vertical-align: bottom;
|
||||
font-size: var(--font-size-06);
|
||||
}
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
hyphens: auto;
|
||||
width: 50%;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.dash-cell-owner {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.dash-cell-date {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
display: none;
|
||||
text-align: right;
|
||||
|
||||
.btn {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dash-cell-date-owner {
|
||||
font-size: $font-size-sm;
|
||||
@include text-truncate();
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.dash-cell-checkbox {
|
||||
width: 4%;
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.dash-cell-owner {
|
||||
width: 21%;
|
||||
}
|
||||
|
||||
.dash-cell-date {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
width: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.dash-cell-checkbox {
|
||||
width: 4%;
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
width: 44%;
|
||||
}
|
||||
|
||||
.dash-cell-owner {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.dash-cell-date {
|
||||
width: 21%;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
display: table-cell;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.project-tools {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.dash-cell-checkbox {
|
||||
width: 3%;
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
.dash-cell-owner {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
.dash-cell-date {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
width: 22%;
|
||||
}
|
||||
|
||||
tbody {
|
||||
.dash-cell-actions {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
.dash-cell-checkbox {
|
||||
width: 3%;
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
.dash-cell-owner {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.dash-cell-date {
|
||||
width: 19%;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
width: 17%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xxl) {
|
||||
.dash-cell-checkbox {
|
||||
width: 2%;
|
||||
}
|
||||
|
||||
.dash-cell-name {
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.dash-cell-owner {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.dash-cell-date {
|
||||
width: 19%;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
width: 14%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
tr {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dash-cell-name,
|
||||
.dash-cell-owner,
|
||||
.dash-cell-date,
|
||||
.dash-cell-tag,
|
||||
.dash-cell-actions {
|
||||
display: block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.dash-cell-actions {
|
||||
position: absolute;
|
||||
top: var(--spacing-04);
|
||||
right: var(--spacing-04);
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.dropdown-table-button-toggle {
|
||||
padding: var(--spacing-04);
|
||||
font-size: 0;
|
||||
line-height: 1;
|
||||
border-radius: 50%;
|
||||
color: var(--content-primary);
|
||||
background-color: transparent;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: rgba($neutral-90, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-list-upload-project-modal-uppy-dashboard .uppy-Root {
|
||||
|
@ -222,3 +460,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-list-load-more-button {
|
||||
margin-bottom: var(--spacing-05);
|
||||
}
|
||||
|
|
|
@ -1763,7 +1763,6 @@
|
|||
"shortcut_to_open_advanced_reference_search": "(<strong>__ctrlSpace__</strong> or <strong>__altSpace__</strong>)",
|
||||
"show_all": "show all",
|
||||
"show_all_projects": "Show all projects",
|
||||
"show_all_uppercase": "Show all",
|
||||
"show_document_preamble": "Show document preamble",
|
||||
"show_hotkeys": "Show Hotkeys",
|
||||
"show_in_code": "Show in code",
|
||||
|
@ -1771,7 +1770,6 @@
|
|||
"show_less": "show less",
|
||||
"show_local_file_contents": "Show Local File Contents",
|
||||
"show_outline": "Show File outline",
|
||||
"show_x_more": "Show __x__ more",
|
||||
"show_x_more_projects": "Show __x__ more projects",
|
||||
"show_your_support": "Show your support",
|
||||
"showing_1_result": "Showing 1 result",
|
||||
|
|
|
@ -72,9 +72,9 @@ describe('<LoadMore />', function () {
|
|||
})
|
||||
|
||||
await waitFor(() => {
|
||||
screen.getByLabelText(
|
||||
`Show ${currentList.length - 20 - 20} more projects`
|
||||
)
|
||||
screen.getByRole('button', {
|
||||
name: `Show ${currentList.length - 20 - 20} more projects`,
|
||||
})
|
||||
screen.getByText(`Showing 40 out of ${currentList.length} projects.`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -202,7 +202,9 @@ describe('<ProjectListRoot />', function () {
|
|||
let checked = allCheckboxes.filter(c => c.checked)
|
||||
expect(checked.length).to.equal(21) // max projects viewable by default is 20, and plus one for check all
|
||||
|
||||
const loadMoreButton = screen.getByLabelText('Show 17 more projects')
|
||||
const loadMoreButton = screen.getByRole('button', {
|
||||
name: 'Show 17 more projects',
|
||||
})
|
||||
fireEvent.click(loadMoreButton)
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
|
@ -212,7 +214,9 @@ describe('<ProjectListRoot />', function () {
|
|||
})
|
||||
|
||||
it('maintains viewable and selected projects after loading more and then selecting all', async function () {
|
||||
const loadMoreButton = screen.getByLabelText('Show 17 more projects')
|
||||
const loadMoreButton = screen.getByRole('button', {
|
||||
name: 'Show 17 more projects',
|
||||
})
|
||||
fireEvent.click(loadMoreButton)
|
||||
// verify button gone
|
||||
screen.getByText(
|
||||
|
@ -225,8 +229,8 @@ describe('<ProjectListRoot />', function () {
|
|||
`Showing ${currentList.length} out of ${currentList.length} projects.`
|
||||
)
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
expect(allCheckboxes.length).to.equal(currentList.length + 1)
|
||||
// allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// expect(allCheckboxes.length).to.equal(currentList.length + 1)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1095,7 +1099,9 @@ describe('<ProjectListRoot />', function () {
|
|||
archived: false,
|
||||
},
|
||||
})
|
||||
const copyButton = within(tableRows[1]).getAllByLabelText('Copy')[0]
|
||||
const copyButton = within(tableRows[1]).getAllByRole('button', {
|
||||
name: 'Copy',
|
||||
})[0]
|
||||
fireEvent.click(copyButton)
|
||||
|
||||
// confirm in modal
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('<ArchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<ArchiveProjectButtonTooltip project={archiveableProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Archive')
|
||||
const btn = screen.getByRole('button', { name: 'Archive' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Archive' })
|
||||
})
|
||||
|
@ -29,7 +29,7 @@ describe('<ArchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<ArchiveProjectButtonTooltip project={archiveableProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Archive')
|
||||
const btn = screen.getByRole('button', { name: 'Archive' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Archive Projects')
|
||||
screen.getByText(archiveableProject.name)
|
||||
|
@ -39,7 +39,7 @@ describe('<ArchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<ArchiveProjectButtonTooltip project={archivedProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Archive')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Archive' })).to.be.null
|
||||
})
|
||||
|
||||
it('should archive the projects', async function () {
|
||||
|
@ -54,7 +54,7 @@ describe('<ArchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<ArchiveProjectButtonTooltip project={project} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Archive')
|
||||
const btn = screen.getByRole('button', { name: 'Archive' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Archive Projects')
|
||||
screen.getByText('You are about to archive the following projects:')
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
|
|||
})
|
||||
|
||||
it('renders tooltip for button', function () {
|
||||
const btn = screen.getByLabelText('Download PDF')
|
||||
const btn = screen.getByRole('button', { name: 'Download PDF' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Download PDF' })
|
||||
})
|
||||
|
@ -49,11 +49,11 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
|
|||
{ delay: 10 }
|
||||
)
|
||||
|
||||
const btn = screen.getByLabelText('Download PDF') as HTMLButtonElement
|
||||
const btn = screen.getByRole('button', { name: 'Download PDF' })
|
||||
fireEvent.click(btn)
|
||||
|
||||
await waitFor(() => {
|
||||
screen.getByLabelText('Compiling…')
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -79,7 +79,9 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
|
|||
status: 'failure',
|
||||
})
|
||||
|
||||
const btn = screen.getByLabelText('Download PDF') as HTMLButtonElement
|
||||
const btn = screen.getByRole('button', {
|
||||
name: 'Download PDF',
|
||||
}) as HTMLButtonElement
|
||||
fireEvent.click(btn)
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('<CopyProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<CopyProjectButtonTooltip project={copyableProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Copy')
|
||||
const btn = screen.getByRole('button', { name: 'Copy' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Copy' })
|
||||
})
|
||||
|
@ -30,17 +30,17 @@ describe('<CopyProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<CopyProjectButtonTooltip project={archivedProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Copy')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Copy' })).to.be.null
|
||||
})
|
||||
|
||||
it('does not render the button when project is trashed', function () {
|
||||
renderWithProjectListContext(
|
||||
<CopyProjectButtonTooltip project={trashedProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Copy')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Copy' })).to.be.null
|
||||
})
|
||||
|
||||
it('opens the modal and copies the project ', async function () {
|
||||
it('opens the modal and copies the project', async function () {
|
||||
const copyProjectMock = fetchMock.post(
|
||||
`express:/project/:projectId/clone`,
|
||||
{
|
||||
|
@ -51,12 +51,14 @@ describe('<CopyProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<CopyProjectButtonTooltip project={copyableProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Copy')
|
||||
const btn = screen.getByRole('button', { name: 'Copy' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Copy Project')
|
||||
screen.getByLabelText('New Name')
|
||||
screen.getByDisplayValue(`${copyableProject.name} (Copy)`)
|
||||
const copyBtn = screen.getByText('Copy') as HTMLButtonElement
|
||||
const copyBtn = screen.getByRole<HTMLButtonElement>('button', {
|
||||
name: 'Copy',
|
||||
})
|
||||
fireEvent.click(copyBtn)
|
||||
expect(copyBtn.disabled).to.be.true
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('<DeleteProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<DeleteProjectButtonTooltip project={trashedProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Delete')
|
||||
const btn = screen.getByRole('button', { name: 'Delete' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Delete' })
|
||||
})
|
||||
|
@ -32,7 +32,7 @@ describe('<DeleteProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<DeleteProjectButtonTooltip project={trashedAndNotOwnedProject} />
|
||||
)
|
||||
const btn = screen.queryByLabelText('Delete')
|
||||
const btn = screen.queryByRole('button', { name: 'Delete' })
|
||||
expect(btn).to.be.null
|
||||
})
|
||||
|
||||
|
@ -40,7 +40,7 @@ describe('<DeleteProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<DeleteProjectButtonTooltip project={archiveableProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Delete')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Delete' })).to.be.null
|
||||
})
|
||||
|
||||
it('opens the modal and deletes the project', async function () {
|
||||
|
@ -56,7 +56,7 @@ describe('<DeleteProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<DeleteProjectButtonTooltip project={project} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Delete')
|
||||
const btn = screen.getByRole('button', { name: 'Delete' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Delete Projects')
|
||||
screen.getByText('You are about to delete the following projects:')
|
||||
|
|
|
@ -23,13 +23,15 @@ describe('<DownloadProjectButton />', function () {
|
|||
})
|
||||
|
||||
it('renders tooltip for button', function () {
|
||||
const btn = screen.getByLabelText('Download .zip file')
|
||||
const btn = screen.getByRole('button', { name: 'Download .zip file' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Download .zip file' })
|
||||
})
|
||||
|
||||
it('downloads the project when clicked', async function () {
|
||||
const btn = screen.getByLabelText('Download .zip file') as HTMLButtonElement
|
||||
const btn = screen.getByRole('button', {
|
||||
name: 'Download .zip file',
|
||||
}) as HTMLButtonElement
|
||||
fireEvent.click(btn)
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('<LeaveProjectButtton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<LeaveProjectButtonTooltip project={trashedAndNotOwnedProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Leave')
|
||||
const btn = screen.getByRole('button', { name: 'Leave' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Leave' })
|
||||
})
|
||||
|
@ -32,7 +32,7 @@ describe('<LeaveProjectButtton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<LeaveProjectButtonTooltip project={trashedProject} />
|
||||
)
|
||||
const btn = screen.queryByLabelText('Leave')
|
||||
const btn = screen.queryByRole('button', { name: 'Leave' })
|
||||
expect(btn).to.be.null
|
||||
})
|
||||
|
||||
|
@ -40,14 +40,14 @@ describe('<LeaveProjectButtton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<LeaveProjectButtonTooltip project={archivedProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Leave')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Leave' })).to.be.null
|
||||
})
|
||||
|
||||
it('does not render the button when project is current', function () {
|
||||
renderWithProjectListContext(
|
||||
<LeaveProjectButtonTooltip project={archiveableProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Leave')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Leave' })).to.be.null
|
||||
})
|
||||
|
||||
it('opens the modal and leaves the project', async function () {
|
||||
|
@ -62,7 +62,7 @@ describe('<LeaveProjectButtton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<LeaveProjectButtonTooltip project={project} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Leave')
|
||||
const btn = screen.getByRole('button', { name: 'Leave' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Leave Projects')
|
||||
screen.getByText('You are about to leave the following projects:')
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('<TrashProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<TrashProjectButtonTooltip project={archivedProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Trash')
|
||||
const btn = screen.getByRole('button', { name: 'Trash' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Trash' })
|
||||
})
|
||||
|
@ -29,7 +29,7 @@ describe('<TrashProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<TrashProjectButtonTooltip project={trashedProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Trash')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Trash' })).to.be.null
|
||||
})
|
||||
|
||||
it('opens the modal and trashes the project', async function () {
|
||||
|
@ -44,7 +44,7 @@ describe('<TrashProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<TrashProjectButtonTooltip project={project} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Trash')
|
||||
const btn = screen.getByRole('button', { name: 'Trash' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Trash Projects')
|
||||
screen.getByText('You are about to trash the following projects:')
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('<UnarchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<UnarchiveProjectButtonTooltip project={archivedProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Restore')
|
||||
const btn = screen.getByRole('button', { name: 'Restore' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Restore' })
|
||||
})
|
||||
|
@ -30,14 +30,14 @@ describe('<UnarchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<UnarchiveProjectButtonTooltip project={trashedProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Restore')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Restore' })).to.be.null
|
||||
})
|
||||
|
||||
it('does not render the button when project is current', function () {
|
||||
renderWithProjectListContext(
|
||||
<UnarchiveProjectButtonTooltip project={archiveableProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Restore')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Restore' })).to.be.null
|
||||
})
|
||||
|
||||
it('unarchive the project and updates the view data', async function () {
|
||||
|
@ -52,7 +52,7 @@ describe('<UnarchiveProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<UnarchiveProjectButtonTooltip project={project} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Restore')
|
||||
const btn = screen.getByRole('button', { name: 'Restore' })
|
||||
fireEvent.click(btn)
|
||||
|
||||
await waitFor(
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('<UntrashProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<UntrashProjectButtonTooltip project={trashedProject} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Restore')
|
||||
const btn = screen.getByRole('button', { name: 'Restore' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Restore' })
|
||||
})
|
||||
|
@ -29,7 +29,7 @@ describe('<UntrashProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<UntrashProjectButtonTooltip project={archiveableProject} />
|
||||
)
|
||||
expect(screen.queryByLabelText('Restore')).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Restore' })).to.be.null
|
||||
})
|
||||
|
||||
it('untrashes the project and updates the view data', async function () {
|
||||
|
@ -44,7 +44,7 @@ describe('<UntrashProjectButton />', function () {
|
|||
renderWithProjectListContext(
|
||||
<UntrashProjectButtonTooltip project={project} />
|
||||
)
|
||||
const btn = screen.getByLabelText('Restore')
|
||||
const btn = screen.getByRole('button', { name: 'Restore' })
|
||||
fireEvent.click(btn)
|
||||
|
||||
await waitFor(
|
||||
|
|
|
@ -108,15 +108,25 @@ describe('<ProjectListTable />', function () {
|
|||
|
||||
// Action Column
|
||||
// temporary count tests until we add filtering for archived/trashed
|
||||
const copyButtons = screen.getAllByLabelText('Copy')
|
||||
const copyButtons = screen.getAllByRole('button', {
|
||||
name: 'Copy',
|
||||
})
|
||||
expect(copyButtons.length).to.equal(currentProjects.length)
|
||||
const downloadButtons = screen.getAllByLabelText('Download .zip file')
|
||||
const downloadButtons = screen.getAllByRole('button', {
|
||||
name: 'Download .zip file',
|
||||
})
|
||||
expect(downloadButtons.length).to.equal(currentProjects.length)
|
||||
const downloadPDFButtons = screen.getAllByLabelText('Download PDF')
|
||||
const downloadPDFButtons = screen.getAllByRole('button', {
|
||||
name: 'Download PDF',
|
||||
})
|
||||
expect(downloadPDFButtons.length).to.equal(currentProjects.length)
|
||||
const archiveButtons = screen.getAllByLabelText('Archive')
|
||||
const archiveButtons = screen.getAllByRole('button', {
|
||||
name: 'Archive',
|
||||
})
|
||||
expect(archiveButtons.length).to.equal(currentProjects.length)
|
||||
const trashButtons = screen.getAllByLabelText('Trash')
|
||||
const trashButtons = screen.getAllByRole('button', {
|
||||
name: 'Trash',
|
||||
})
|
||||
expect(trashButtons.length).to.equal(currentProjects.length)
|
||||
|
||||
// TODO to be implemented when the component renders trashed & archived projects
|
||||
|
|
|
@ -12,14 +12,14 @@ describe('<ArchiveProjectsButton />', function () {
|
|||
|
||||
it('renders tooltip for button', function () {
|
||||
renderWithProjectListContext(<ArchiveProjectsButton />)
|
||||
const btn = screen.getByLabelText('Archive')
|
||||
const btn = screen.getByRole('button', { name: 'Archive' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Archive' })
|
||||
})
|
||||
|
||||
it('opens the modal when clicked', function () {
|
||||
renderWithProjectListContext(<ArchiveProjectsButton />)
|
||||
const btn = screen.getByLabelText('Archive')
|
||||
const btn = screen.getByRole('button', { name: 'Archive' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Archive Projects')
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ describe('<DownloadProjectsButton />', function () {
|
|||
|
||||
it('renders tooltip for button', function () {
|
||||
renderWithProjectListContext(<DownloadProjectsButton />)
|
||||
const btn = screen.getByLabelText('Download')
|
||||
const btn = screen.getByRole('button', { name: 'Download' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Download' })
|
||||
})
|
||||
|
|
|
@ -12,14 +12,14 @@ describe('<TrashProjectsButton />', function () {
|
|||
|
||||
it('renders tooltip for button', function () {
|
||||
renderWithProjectListContext(<TrashProjectsButton />)
|
||||
const btn = screen.getByLabelText('Trash')
|
||||
const btn = screen.getByRole('button', { name: 'Trash' })
|
||||
fireEvent.mouseOver(btn)
|
||||
screen.getByRole('tooltip', { name: 'Trash' })
|
||||
})
|
||||
|
||||
it('opens the modal when clicked', function () {
|
||||
renderWithProjectListContext(<TrashProjectsButton />)
|
||||
const btn = screen.getByLabelText('Trash')
|
||||
const btn = screen.getByRole('button', { name: 'Trash' })
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Trash Projects')
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue