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