mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #19840 from overleaf/ii-bs5-project-tools
[web] BS5 project tools GitOrigin-RevId: 3181c62985b6db4051292b484f53178a0736fa75
This commit is contained in:
parent
22fb9973fa
commit
0e71084600
54 changed files with 1379 additions and 585 deletions
|
@ -41,7 +41,7 @@ describe('Project List', () => {
|
||||||
cy.visit('/project')
|
cy.visit('/project')
|
||||||
|
|
||||||
findProjectRow(projectName).within(() =>
|
findProjectRow(projectName).within(() =>
|
||||||
cy.contains(`Download .zip file`).click()
|
cy.findByRole('button', { name: 'Download .zip file' }).click()
|
||||||
)
|
)
|
||||||
|
|
||||||
cy.task('readFileInZip', {
|
cy.task('readFileInZip', {
|
||||||
|
@ -55,7 +55,7 @@ describe('Project List', () => {
|
||||||
cy.visit('/project')
|
cy.visit('/project')
|
||||||
|
|
||||||
findProjectRow(projectName).within(() =>
|
findProjectRow(projectName).within(() =>
|
||||||
cy.contains(`Download PDF`).click()
|
cy.findByRole('button', { name: 'Download PDF' }).click()
|
||||||
)
|
)
|
||||||
|
|
||||||
const pdfName = projectName.replaceAll('-', '_')
|
const pdfName = projectName.replaceAll('-', '_')
|
||||||
|
|
|
@ -446,6 +446,7 @@
|
||||||
"files_cannot_include_invalid_characters": "",
|
"files_cannot_include_invalid_characters": "",
|
||||||
"files_selected": "",
|
"files_selected": "",
|
||||||
"fill_in_our_quick_survey": "",
|
"fill_in_our_quick_survey": "",
|
||||||
|
"filter_projects": "",
|
||||||
"find_out_more": "",
|
"find_out_more": "",
|
||||||
"find_out_more_about_institution_login": "",
|
"find_out_more_about_institution_login": "",
|
||||||
"find_out_more_about_the_file_outline": "",
|
"find_out_more_about_the_file_outline": "",
|
||||||
|
@ -1298,6 +1299,7 @@
|
||||||
"sorry_your_table_cant_be_displayed_at_the_moment": "",
|
"sorry_your_table_cant_be_displayed_at_the_moment": "",
|
||||||
"sort_by": "",
|
"sort_by": "",
|
||||||
"sort_by_x": "",
|
"sort_by_x": "",
|
||||||
|
"sort_projects": "",
|
||||||
"source": "",
|
"source": "",
|
||||||
"spell_check": "",
|
"spell_check": "",
|
||||||
"sso": "",
|
"sso": "",
|
||||||
|
@ -1499,6 +1501,10 @@
|
||||||
"toolbar_insert_table": "",
|
"toolbar_insert_table": "",
|
||||||
"toolbar_numbered_list": "",
|
"toolbar_numbered_list": "",
|
||||||
"toolbar_redo": "",
|
"toolbar_redo": "",
|
||||||
|
"toolbar_selected_projects": "",
|
||||||
|
"toolbar_selected_projects_management_actions": "",
|
||||||
|
"toolbar_selected_projects_remove": "",
|
||||||
|
"toolbar_selected_projects_restore": "",
|
||||||
"toolbar_table_insert_size_table": "",
|
"toolbar_table_insert_size_table": "",
|
||||||
"toolbar_table_insert_table_lowercase": "",
|
"toolbar_table_insert_table_lowercase": "",
|
||||||
"toolbar_toggle_symbol_palette": "",
|
"toolbar_toggle_symbol_palette": "",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { CommonsPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
import { CommonsPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||||
import Tooltip from '../../../../shared/components/tooltip'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
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'
|
||||||
|
|
||||||
type CommonsPlanProps = Pick<
|
type CommonsPlanProps = Pick<
|
||||||
CommonsPlanSubscription,
|
CommonsPlanSubscription,
|
||||||
|
@ -19,8 +23,15 @@ function CommonsPlan({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="current-plan-label visible-xs">{currentPlanLabel}</span>
|
<span
|
||||||
<Tooltip
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
</span>
|
||||||
|
<OLTooltip
|
||||||
description={t('commons_plan_tooltip', {
|
description={t('commons_plan_tooltip', {
|
||||||
plan: plan.name,
|
plan: plan.name,
|
||||||
institution: subscription.name,
|
institution: subscription.name,
|
||||||
|
@ -28,10 +39,25 @@ function CommonsPlan({
|
||||||
id="commons-plan"
|
id="commons-plan"
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<a href={featuresPageURL} className="current-plan-label hidden-xs">
|
<a
|
||||||
{currentPlanLabel} <span className="info-badge" />
|
href={featuresPageURL}
|
||||||
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({
|
||||||
|
bs5: 'd-none d-md-inline-block',
|
||||||
|
bs3: 'hidden-xs',
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<span className="info-badge" />}
|
||||||
|
bs5={
|
||||||
|
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { Button } from 'react-bootstrap'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
import { FreePlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
import { FreePlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||||
import Tooltip from '../../../../shared/components/tooltip'
|
|
||||||
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
type FreePlanProps = Pick<FreePlanSubscription, 'featuresPageURL'>
|
type FreePlanProps = Pick<FreePlanSubscription, 'featuresPageURL'>
|
||||||
|
|
||||||
|
@ -23,24 +27,49 @@ function FreePlan({ featuresPageURL }: FreePlanProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="current-plan-label visible-xs">{currentPlanLabel}</span>
|
<span
|
||||||
<Tooltip
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
</span>
|
||||||
|
<OLTooltip
|
||||||
description={t('free_plan_tooltip')}
|
description={t('free_plan_tooltip')}
|
||||||
id="free-plan"
|
id="free-plan"
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<a href={featuresPageURL} className="current-plan-label hidden-xs">
|
<a
|
||||||
{currentPlanLabel} <span className="info-badge" />
|
href={featuresPageURL}
|
||||||
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({ bs5: 'd-none d-md-inline-block', bs3: 'hidden-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<span className="info-badge" />}
|
||||||
|
bs5={
|
||||||
|
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>{' '}
|
</OLTooltip>{' '}
|
||||||
<Button
|
<span
|
||||||
bsStyle="primary"
|
className={bsVersion({
|
||||||
className="hidden-xs"
|
bs5: 'd-none d-md-inline-block',
|
||||||
|
bs3: 'hidden-xs',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<OLButton
|
||||||
|
variant="primary"
|
||||||
href="/user/subscription/plans"
|
href="/user/subscription/plans"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{t('upgrade')}
|
{t('upgrade')}
|
||||||
</Button>
|
</OLButton>
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { GroupPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
import { GroupPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||||
import Tooltip from '../../../../shared/components/tooltip'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
type GroupPlanProps = Pick<
|
type GroupPlanProps = Pick<
|
||||||
GroupPlanSubscription,
|
GroupPlanSubscription,
|
||||||
|
@ -33,8 +37,15 @@ function GroupPlan({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="current-plan-label visible-xs">{currentPlanLabel}</span>
|
<span
|
||||||
<Tooltip
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
</span>
|
||||||
|
<OLTooltip
|
||||||
description={
|
description={
|
||||||
subscription.teamName != null
|
subscription.teamName != null
|
||||||
? t('group_plan_with_name_tooltip', {
|
? t('group_plan_with_name_tooltip', {
|
||||||
|
@ -46,10 +57,25 @@ function GroupPlan({
|
||||||
id="group-plan"
|
id="group-plan"
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<a href={featuresPageURL} className="current-plan-label hidden-xs">
|
<a
|
||||||
{currentPlanLabel} <span className="info-badge" />
|
href={featuresPageURL}
|
||||||
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({
|
||||||
|
bs5: 'd-none d-md-inline-block',
|
||||||
|
bs3: 'hidden-xs',
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<span className="info-badge" />}
|
||||||
|
bs5={
|
||||||
|
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { IndividualPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
import { IndividualPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||||
import Tooltip from '../../../../shared/components/tooltip'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
type IndividualPlanProps = Pick<
|
type IndividualPlanProps = Pick<
|
||||||
IndividualPlanSubscription,
|
IndividualPlanSubscription,
|
||||||
|
@ -32,16 +36,35 @@ function IndividualPlan({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="current-plan-label visible-xs">{currentPlanLabel}</span>
|
<span
|
||||||
<Tooltip
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
</span>
|
||||||
|
<OLTooltip
|
||||||
description={t('plan_tooltip', { plan: plan.name })}
|
description={t('plan_tooltip', { plan: plan.name })}
|
||||||
id="individual-plan"
|
id="individual-plan"
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<a href={featuresPageURL} className="current-plan-label hidden-xs">
|
<a
|
||||||
{currentPlanLabel} <span className="info-badge" />
|
href={featuresPageURL}
|
||||||
|
className={classnames(
|
||||||
|
'current-plan-label',
|
||||||
|
bsVersion({ bs5: 'd-none d-md-inline-block', bs3: 'hidden-xs' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentPlanLabel}
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<span className="info-badge" />}
|
||||||
|
bs5={
|
||||||
|
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,7 +318,7 @@ function BS5ActionsDropdown({ project }: ActionDropdownProps) {
|
||||||
as="button"
|
as="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={downloadProject}
|
onClick={downloadProject}
|
||||||
leadingIcon="cloud_download"
|
leadingIcon="download"
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
|
@ -1,32 +1,45 @@
|
||||||
import { useState, useEffect, useRef } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dropdown, MenuItem } from 'react-bootstrap'
|
import {
|
||||||
|
Dropdown as BS3Dropdown,
|
||||||
|
MenuItem as BS3MenuItem,
|
||||||
|
} from 'react-bootstrap'
|
||||||
import Icon from '../../../../shared/components/icon'
|
import Icon from '../../../../shared/components/icon'
|
||||||
import {
|
import {
|
||||||
Filter,
|
Filter,
|
||||||
UNCATEGORIZED_KEY,
|
UNCATEGORIZED_KEY,
|
||||||
useProjectListContext,
|
useProjectListContext,
|
||||||
} from '../../context/project-list-context'
|
} from '../../context/project-list-context'
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownHeader,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
import ProjectsFilterMenu from '../projects-filter-menu'
|
import ProjectsFilterMenu from '../projects-filter-menu'
|
||||||
import TagsList from '../tags-list'
|
import TagsList from '../tags-list'
|
||||||
import MenuItemButton from './menu-item-button'
|
import MenuItemButton from './menu-item-button'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
filter: Filter
|
filter: Filter
|
||||||
text: string
|
text: string
|
||||||
onClick: () => void
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Item({ filter, text, onClick }: ItemProps) {
|
export function Item({ filter, text, onClick }: ItemProps) {
|
||||||
const { selectFilter } = useProjectListContext()
|
const { selectFilter } = useProjectListContext()
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
selectFilter(filter)
|
selectFilter(filter)
|
||||||
onClick()
|
onClick?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectsFilterMenu filter={filter}>
|
<ProjectsFilterMenu filter={filter}>
|
||||||
{isActive => (
|
{isActive => (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
<MenuItemButton
|
<MenuItemButton
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="projects-types-menu-item"
|
className="projects-types-menu-item"
|
||||||
|
@ -36,6 +49,19 @@ export function Item({ filter, text, onClick }: ItemProps) {
|
||||||
) : null}
|
) : null}
|
||||||
<span className="menu-item-button-text">{text}</span>
|
<span className="menu-item-button-text">{text}</span>
|
||||||
</MenuItemButton>
|
</MenuItemButton>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={handleClick}
|
||||||
|
trailingIcon={isActive ? 'check' : undefined}
|
||||||
|
active={isActive}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</DropdownItem>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</ProjectsFilterMenu>
|
</ProjectsFilterMenu>
|
||||||
)
|
)
|
||||||
|
@ -72,18 +98,28 @@ function ProjectsDropdown() {
|
||||||
}, [filter, tags, selectedTagId, t])
|
}, [filter, tags, selectedTagId, t])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
|
<BS3Dropdown
|
||||||
id="projects-types-dropdown"
|
id="projects-types-dropdown"
|
||||||
open={isOpened}
|
open={isOpened}
|
||||||
onToggle={open => setIsOpened(open)}
|
onToggle={open => setIsOpened(open)}
|
||||||
>
|
>
|
||||||
<Dropdown.Toggle bsSize="large" noCaret className="ps-0 btn-transparent">
|
<BS3Dropdown.Toggle
|
||||||
|
bsSize="large"
|
||||||
|
noCaret
|
||||||
|
className="ps-0 btn-transparent"
|
||||||
|
>
|
||||||
<span className="text-truncate me-1">{title}</span>
|
<span className="text-truncate me-1">{title}</span>
|
||||||
<Icon type="angle-down" />
|
<Icon type="angle-down" />
|
||||||
</Dropdown.Toggle>
|
</BS3Dropdown.Toggle>
|
||||||
<Dropdown.Menu className="projects-dropdown-menu">
|
<BS3Dropdown.Menu className="projects-dropdown-menu">
|
||||||
<Item filter="all" text={t('all_projects')} onClick={handleClose} />
|
<Item filter="all" text={t('all_projects')} onClick={handleClose} />
|
||||||
<Item filter="owned" text={t('your_projects')} onClick={handleClose} />
|
<Item
|
||||||
|
filter="owned"
|
||||||
|
text={t('your_projects')}
|
||||||
|
onClick={handleClose}
|
||||||
|
/>
|
||||||
<Item
|
<Item
|
||||||
filter="shared"
|
filter="shared"
|
||||||
text={t('shared_with_you')}
|
text={t('shared_with_you')}
|
||||||
|
@ -99,10 +135,47 @@ function ProjectsDropdown() {
|
||||||
text={t('trashed_projects')}
|
text={t('trashed_projects')}
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
/>
|
/>
|
||||||
<MenuItem header>{t('tags')}:</MenuItem>
|
<BS3MenuItem header>{t('tags')}:</BS3MenuItem>
|
||||||
<TagsList onTagClick={handleClose} onEditClick={handleClose} />
|
<TagsList onTagClick={handleClose} onEditClick={handleClose} />
|
||||||
</Dropdown.Menu>
|
</BS3Dropdown.Menu>
|
||||||
|
</BS3Dropdown>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownToggle
|
||||||
|
id="projects-types-dropdown-toggle-btn"
|
||||||
|
className="ps-0 mb-0 btn-transparent h3"
|
||||||
|
size="lg"
|
||||||
|
aria-label={t('filter_projects')}
|
||||||
|
>
|
||||||
|
<span className="text-truncate" aria-hidden>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu flip={false}>
|
||||||
|
<li role="none">
|
||||||
|
<Item filter="all" text={t('all_projects')} />
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<Item filter="owned" text={t('your_projects')} />
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<Item filter="shared" text={t('shared_with_you')} />
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<Item filter="archived" text={t('archived_projects')} />
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<Item filter="trashed" text={t('trashed_projects')} />
|
||||||
|
</li>
|
||||||
|
<DropdownHeader className="text-uppercase">
|
||||||
|
{t('tags')}:
|
||||||
|
</DropdownHeader>
|
||||||
|
<TagsList />
|
||||||
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
import { useState, useEffect, useRef } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dropdown, MenuItem } from 'react-bootstrap'
|
import {
|
||||||
|
Dropdown as BS3Dropdown,
|
||||||
|
MenuItem as BS3MenuItem,
|
||||||
|
} from 'react-bootstrap'
|
||||||
import Icon from '../../../../shared/components/icon'
|
import Icon from '../../../../shared/components/icon'
|
||||||
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 { useProjectListContext } from '../../context/project-list-context'
|
import { useProjectListContext } from '../../context/project-list-context'
|
||||||
import { Sort } from '../../../../../../types/project/dashboard/api'
|
import { Sort } from '../../../../../../types/project/dashboard/api'
|
||||||
import MenuItemButton from './menu-item-button'
|
import MenuItemButton from './menu-item-button'
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownHeader,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
function Item({ onClick, text, iconType, screenReaderText }: SortBtnProps) {
|
function Item({ onClick, text, iconType, screenReaderText }: SortBtnProps) {
|
||||||
return (
|
return (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
<MenuItemButton onClick={onClick} className="projects-sort-menu-item">
|
<MenuItemButton onClick={onClick} className="projects-sort-menu-item">
|
||||||
{iconType ? (
|
{iconType ? (
|
||||||
<Icon type={iconType} className="menu-item-button-icon" />
|
<Icon type={iconType} className="menu-item-button-icon" />
|
||||||
|
@ -17,6 +30,18 @@ function Item({ onClick, text, iconType, screenReaderText }: SortBtnProps) {
|
||||||
<span className="menu-item-button-text">{text}</span>
|
<span className="menu-item-button-text">{text}</span>
|
||||||
<span className="sr-only">{screenReaderText}</span>
|
<span className="sr-only">{screenReaderText}</span>
|
||||||
</MenuItemButton>
|
</MenuItemButton>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={onClick}
|
||||||
|
trailingIcon={iconType}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</DropdownItem>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,25 +64,35 @@ function SortByDropdown() {
|
||||||
setIsOpened(false)
|
setIsOpened(false)
|
||||||
handleSort(by)
|
handleSort(by)
|
||||||
}
|
}
|
||||||
|
const handleClickBS5 = (by: Sort['by']) => {
|
||||||
|
setTitle(sortByTranslations.current[by])
|
||||||
|
handleSort(by)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(sortByTranslations.current[sort.by])
|
setTitle(sortByTranslations.current[sort.by])
|
||||||
}, [sort.by])
|
}, [sort.by])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
|
<BS3Dropdown
|
||||||
id="projects-sort-dropdown"
|
id="projects-sort-dropdown"
|
||||||
className="projects-sort-dropdown"
|
className="projects-sort-dropdown"
|
||||||
pullRight
|
pullRight
|
||||||
open={isOpened}
|
open={isOpened}
|
||||||
onToggle={open => setIsOpened(open)}
|
onToggle={open => setIsOpened(open)}
|
||||||
>
|
>
|
||||||
<Dropdown.Toggle bsSize="small" noCaret className="pe-0 btn-transparent">
|
<BS3Dropdown.Toggle
|
||||||
|
bsSize="small"
|
||||||
|
noCaret
|
||||||
|
className="pe-0 btn-transparent"
|
||||||
|
>
|
||||||
<span className="text-truncate me-1">{title}</span>
|
<span className="text-truncate me-1">{title}</span>
|
||||||
<Icon type="angle-down" />
|
<Icon type="angle-down" />
|
||||||
</Dropdown.Toggle>
|
</BS3Dropdown.Toggle>
|
||||||
<Dropdown.Menu className="projects-dropdown-menu">
|
<BS3Dropdown.Menu className="projects-dropdown-menu">
|
||||||
<MenuItem header>{t('sort_by')}:</MenuItem>
|
<BS3MenuItem header>{t('sort_by')}:</BS3MenuItem>
|
||||||
<ItemWithContent
|
<ItemWithContent
|
||||||
column="title"
|
column="title"
|
||||||
text={t('title')}
|
text={t('title')}
|
||||||
|
@ -76,8 +111,47 @@ function SortByDropdown() {
|
||||||
sort={sort}
|
sort={sort}
|
||||||
onClick={() => handleClick('lastUpdated')}
|
onClick={() => handleClick('lastUpdated')}
|
||||||
/>
|
/>
|
||||||
</Dropdown.Menu>
|
</BS3Dropdown.Menu>
|
||||||
|
</BS3Dropdown>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<Dropdown className="projects-sort-dropdown" align="end">
|
||||||
|
<DropdownToggle
|
||||||
|
id="projects-sort-dropdown"
|
||||||
|
className="pe-0 mb-0 btn-transparent"
|
||||||
|
size="sm"
|
||||||
|
aria-label={t('sort_projects')}
|
||||||
|
>
|
||||||
|
<span className="text-truncate" aria-hidden>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu flip={false}>
|
||||||
|
<DropdownHeader className="text-uppercase">
|
||||||
|
{t('sort_by')}:
|
||||||
|
</DropdownHeader>
|
||||||
|
<ItemWithContent
|
||||||
|
column="title"
|
||||||
|
text={t('title')}
|
||||||
|
sort={sort}
|
||||||
|
onClick={() => handleClickBS5('title')}
|
||||||
|
/>
|
||||||
|
<ItemWithContent
|
||||||
|
column="owner"
|
||||||
|
text={t('owner')}
|
||||||
|
sort={sort}
|
||||||
|
onClick={() => handleClickBS5('owner')}
|
||||||
|
/>
|
||||||
|
<ItemWithContent
|
||||||
|
column="lastUpdated"
|
||||||
|
text={t('last_modified')}
|
||||||
|
sort={sort}
|
||||||
|
onClick={() => handleClickBS5('lastUpdated')}
|
||||||
|
/>
|
||||||
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ import OLModal, {
|
||||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||||
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
|
||||||
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
||||||
import OLForm from '@/features/ui/components/ol/ol-form'
|
import OLForm from '@/features/ui/components/ol/ol-form'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
type CreateTagModalProps = {
|
type CreateTagModalProps = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -104,10 +104,10 @@ export default function CreateTagModal({
|
||||||
</OLFormGroup>
|
</OLFormGroup>
|
||||||
</OLForm>
|
</OLForm>
|
||||||
{validationError && (
|
{validationError && (
|
||||||
<OLNotification type="error" content={validationError} />
|
<Notification type="error" content={validationError} />
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<OLNotification
|
<Notification
|
||||||
type="error"
|
type="error"
|
||||||
content={t('generic_something_went_wrong')}
|
content={t('generic_something_went_wrong')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import OLModal, {
|
||||||
OLModalTitle,
|
OLModalTitle,
|
||||||
} from '@/features/ui/components/ol/ol-modal'
|
} from '@/features/ui/components/ol/ol-modal'
|
||||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
type DeleteTagModalProps = {
|
type DeleteTagModalProps = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -56,7 +56,7 @@ export default function DeleteTagModal({
|
||||||
<li>{tag.name}</li>
|
<li>{tag.name}</li>
|
||||||
</ul>
|
</ul>
|
||||||
{isError && (
|
{isError && (
|
||||||
<OLNotification
|
<Notification
|
||||||
type="error"
|
type="error"
|
||||||
content={t('generic_something_went_wrong')}
|
content={t('generic_something_went_wrong')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,7 +19,7 @@ import OLForm from '@/features/ui/components/ol/ol-form'
|
||||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||||
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
import Notification from '@/shared/components/notification'
|
||||||
|
|
||||||
type EditTagModalProps = {
|
type EditTagModalProps = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -113,12 +113,12 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
|
||||||
</OLFormGroup>
|
</OLFormGroup>
|
||||||
</OLForm>
|
</OLForm>
|
||||||
{validationError && (
|
{validationError && (
|
||||||
<OLNotification type="error" content={validationError} />
|
<Notification content={validationError} type="error" />
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<OLNotification
|
<Notification
|
||||||
type="error"
|
|
||||||
content={t('generic_something_went_wrong')}
|
content={t('generic_something_went_wrong')}
|
||||||
|
type="error"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</OLModalBody>
|
</OLModalBody>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Button, ControlLabel, Form, FormGroup, Modal } from 'react-bootstrap'
|
|
||||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
|
||||||
import useAsync from '../../../../shared/hooks/use-async'
|
import useAsync from '../../../../shared/hooks/use-async'
|
||||||
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
||||||
import useSelectColor from '../../hooks/use-select-color'
|
import useSelectColor from '../../hooks/use-select-color'
|
||||||
|
@ -10,6 +8,19 @@ import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||||
import { getTagColor } from '../../util/tag'
|
import { getTagColor } from '../../util/tag'
|
||||||
import { ColorPicker } from '../color-picker/color-picker'
|
import { ColorPicker } from '../color-picker/color-picker'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import OLModal, {
|
||||||
|
OLModalBody,
|
||||||
|
OLModalFooter,
|
||||||
|
OLModalHeader,
|
||||||
|
OLModalTitle,
|
||||||
|
} from '@/features/ui/components/ol/ol-modal'
|
||||||
|
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 OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
type ManageTagModalProps = {
|
type ManageTagModalProps = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -78,14 +89,14 @@ export function ManageTagModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleModal show animation onHide={onClose} id={id} backdrop="static">
|
<OLModal show animation onHide={onClose} id={id} backdrop="static">
|
||||||
<Modal.Header closeButton>
|
<OLModalHeader closeButton>
|
||||||
<Modal.Title>{t('edit_tag')}</Modal.Title>
|
<OLModalTitle>{t('edit_tag')}</OLModalTitle>
|
||||||
</Modal.Header>
|
</OLModalHeader>
|
||||||
|
|
||||||
<Modal.Body>
|
<OLModalBody>
|
||||||
<Form name="editTagRenameForm" onSubmit={handleSubmit}>
|
<OLForm onSubmit={handleSubmit}>
|
||||||
<FormGroup>
|
<OLFormGroup>
|
||||||
<input
|
<input
|
||||||
ref={autoFocusedRef}
|
ref={autoFocusedRef}
|
||||||
className="form-control"
|
className="form-control"
|
||||||
|
@ -96,61 +107,60 @@ export function ManageTagModal({
|
||||||
required
|
required
|
||||||
onChange={e => setNewTagName(e.target.value)}
|
onChange={e => setNewTagName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</OLFormGroup>
|
||||||
<FormGroup aria-hidden="true">
|
<OLFormGroup aria-hidden="true">
|
||||||
<ControlLabel>{t('tag_color')}</ControlLabel>:<br />
|
<OLFormLabel>{t('tag_color')}</OLFormLabel>:<br />
|
||||||
<ColorPicker disableCustomColor />
|
<ColorPicker disableCustomColor />
|
||||||
</FormGroup>
|
</OLFormGroup>
|
||||||
</Form>
|
</OLForm>
|
||||||
</Modal.Body>
|
{(isDeleteError || isRenameError) && (
|
||||||
|
<Notification
|
||||||
<Modal.Footer>
|
type="error"
|
||||||
<div className="clearfix">
|
content={t('generic_something_went_wrong')}
|
||||||
<div className="modal-footer-left">
|
/>
|
||||||
<Button
|
|
||||||
onClick={() => runDeleteTag(tag._id)}
|
|
||||||
bsStyle="danger"
|
|
||||||
disabled={isDeleteLoading || isUpdateLoading}
|
|
||||||
>
|
|
||||||
{isDeleteLoading ? (
|
|
||||||
<>{t('deleting')} …</>
|
|
||||||
) : (
|
|
||||||
t('delete_tag')
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
</OLModalBody>
|
||||||
</div>
|
|
||||||
<Button
|
<OLModalFooter>
|
||||||
|
<OLButton
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => runDeleteTag(tag._id)}
|
||||||
|
className={bsVersion({ bs3: 'pull-left', bs5: 'me-auto' })}
|
||||||
|
disabled={isDeleteLoading || isUpdateLoading}
|
||||||
|
isLoading={isDeleteLoading}
|
||||||
|
bs3Props={{
|
||||||
|
loading: isDeleteLoading ? `${t('deleting')}…` : t('delete_tag'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('delete_tag')}
|
||||||
|
</OLButton>
|
||||||
|
<OLButton
|
||||||
|
variant="secondary"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isDeleteLoading || isUpdateLoading}
|
disabled={isDeleteLoading || isUpdateLoading}
|
||||||
>
|
>
|
||||||
{t('save_or_cancel-cancel')}
|
{t('save_or_cancel-cancel')}
|
||||||
</Button>
|
</OLButton>
|
||||||
<Button
|
<OLButton
|
||||||
|
variant="primary"
|
||||||
onClick={() => runUpdateTag(tag._id)}
|
onClick={() => runUpdateTag(tag._id)}
|
||||||
bsStyle={null}
|
|
||||||
className="btn-secondary"
|
|
||||||
disabled={Boolean(
|
disabled={Boolean(
|
||||||
isUpdateLoading ||
|
isUpdateLoading ||
|
||||||
isDeleteLoading ||
|
isDeleteLoading ||
|
||||||
!newTagName?.length ||
|
!newTagName?.length ||
|
||||||
(newTagName === tag?.name && selectedColor === getTagColor(tag))
|
(newTagName === tag?.name && selectedColor === getTagColor(tag))
|
||||||
)}
|
)}
|
||||||
|
isLoading={isUpdateLoading}
|
||||||
|
bs3Props={{
|
||||||
|
loading: isUpdateLoading
|
||||||
|
? `${t('saving')}…`
|
||||||
|
: t('save_or_cancel-save'),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isUpdateLoading ? (
|
{t('save_or_cancel-save')}
|
||||||
<>{t('saving')} …</>
|
</OLButton>
|
||||||
) : (
|
<BootstrapVersionSwitcher bs3={<div className="clearfix" />} />
|
||||||
t('save_or_cancel-save')
|
</OLModalFooter>
|
||||||
)}
|
</OLModal>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{(isDeleteError || isRenameError) && (
|
|
||||||
<div className="modal-footer-left mt-2">
|
|
||||||
<span className="text-danger error">
|
|
||||||
{t('generic_something_went_wrong')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Modal.Footer>
|
|
||||||
</AccessibleModal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,7 +191,11 @@ function ProjectListPageContent() {
|
||||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div role="toolbar" className="projects-toolbar">
|
<div
|
||||||
|
role="toolbar"
|
||||||
|
className="projects-toolbar"
|
||||||
|
aria-label={t('projects')}
|
||||||
|
>
|
||||||
<ProjectsDropdown />
|
<ProjectsDropdown />
|
||||||
<SortByDropdown />
|
<SortByDropdown />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,7 +54,7 @@ const DownloadProjectButtonTooltip = memo(
|
||||||
className="action-btn"
|
className="action-btn"
|
||||||
icon={
|
icon={
|
||||||
bsVersion({
|
bsVersion({
|
||||||
bs5: 'cloud_download',
|
bs5: 'download',
|
||||||
bs3: 'cloud-download',
|
bs3: 'cloud-download',
|
||||||
}) as string
|
}) as string
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import Icon from '../../../../../shared/components/icon'
|
||||||
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'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
|
||||||
type LinkSharingIconProps = {
|
type LinkSharingIconProps = {
|
||||||
prependSpace: boolean
|
prependSpace: boolean
|
||||||
|
@ -26,11 +28,22 @@ function LinkSharingIcon({
|
||||||
{/* OverlayTrigger won't fire unless icon is wrapped in a span */}
|
{/* OverlayTrigger won't fire unless icon is wrapped in a span */}
|
||||||
<span className={className}>
|
<span className={className}>
|
||||||
{prependSpace ? ' ' : ''}
|
{prependSpace ? ' ' : ''}
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
<Icon
|
<Icon
|
||||||
type="link"
|
type="link"
|
||||||
className="small"
|
className="small"
|
||||||
accessibilityLabel={t('link_sharing')}
|
accessibilityLabel={t('link_sharing')}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<MaterialIcon
|
||||||
|
type="link"
|
||||||
|
className="align-text-bottom"
|
||||||
|
accessibilityLabel={t('link_sharing')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</OLTooltip>
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
|
@ -48,14 +61,8 @@ export default function OwnerCell({ project }: OwnerCellProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ownerName === 'You' ? t('you') : ownerName}
|
{ownerName === 'You' ? t('you') : ownerName}
|
||||||
{project.source === 'token' ? (
|
{project.source === 'token' && (
|
||||||
<LinkSharingIcon
|
<LinkSharingIcon project={project} prependSpace={!!project.owner} />
|
||||||
className="hidden-xs"
|
|
||||||
project={project}
|
|
||||||
prependSpace={!!project.owner}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
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 OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||||
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 { Project } from '../../../../../../../../types/project/dashboard/api'
|
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
function ArchiveProjectsButton() {
|
function ArchiveProjectsButton() {
|
||||||
const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
|
const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
|
||||||
|
@ -40,19 +41,23 @@ function ArchiveProjectsButton() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="tooltip-archive-projects"
|
id="tooltip-archive-projects"
|
||||||
description={text}
|
description={text}
|
||||||
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
||||||
>
|
>
|
||||||
<button
|
<OLIconButton
|
||||||
className="btn btn-secondary"
|
|
||||||
aria-label={text}
|
|
||||||
onClick={handleOpenModal}
|
onClick={handleOpenModal}
|
||||||
>
|
variant="secondary"
|
||||||
<Icon type="inbox" />
|
accessibilityLabel={text}
|
||||||
</button>
|
icon={
|
||||||
</Tooltip>
|
bsVersion({
|
||||||
|
bs5: 'inbox',
|
||||||
|
bs3: 'inbox',
|
||||||
|
}) as string
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</OLTooltip>
|
||||||
<ArchiveProjectModal
|
<ArchiveProjectModal
|
||||||
projects={selectedProjects}
|
projects={selectedProjects}
|
||||||
actionHandler={handleArchiveProject}
|
actionHandler={handleArchiveProject}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import DeleteLeaveProjectModal from '../../../modals/delete-leave-project-modal'
|
import DeleteLeaveProjectModal from '../../../modals/delete-leave-project-modal'
|
||||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||||
|
@ -41,9 +41,9 @@ function DeleteLeaveProjectsButton() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasDeletableProjectsSelected && hasLeavableProjectsSelected && (
|
{hasDeletableProjectsSelected && hasLeavableProjectsSelected && (
|
||||||
<Button bsStyle={null} className="btn-danger" onClick={handleOpenModal}>
|
<OLButton variant="danger" onClick={handleOpenModal}>
|
||||||
{t('delete_and_leave')}
|
{t('delete_and_leave')}
|
||||||
</Button>
|
</OLButton>
|
||||||
)}
|
)}
|
||||||
<DeleteLeaveProjectModal
|
<DeleteLeaveProjectModal
|
||||||
projects={selectedProjects}
|
projects={selectedProjects}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
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'
|
||||||
|
@ -37,9 +37,9 @@ function DeleteProjectsButton() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasDeletableProjectsSelected && !hasLeavableProjectsSelected && (
|
{hasDeletableProjectsSelected && !hasLeavableProjectsSelected && (
|
||||||
<Button bsStyle={null} className="btn-danger" onClick={handleOpenModal}>
|
<OLButton variant="danger" onClick={handleOpenModal}>
|
||||||
{t('delete')}
|
{t('delete')}
|
||||||
</Button>
|
</OLButton>
|
||||||
)}
|
)}
|
||||||
<DeleteProjectModal
|
<DeleteProjectModal
|
||||||
projects={selectedProjects}
|
projects={selectedProjects}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { memo, useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Icon from '../../../../../../shared/components/icon'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||||
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||||
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 { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
function DownloadProjectsButton() {
|
function DownloadProjectsButton() {
|
||||||
const { selectedProjects, selectOrUnselectAllProjects } =
|
const { selectedProjects, selectOrUnselectAllProjects } =
|
||||||
|
@ -29,19 +30,23 @@ function DownloadProjectsButton() {
|
||||||
}, [projectIds, selectOrUnselectAllProjects, location])
|
}, [projectIds, selectOrUnselectAllProjects, location])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="tooltip-download-projects"
|
id="tooltip-download-projects"
|
||||||
description={text}
|
description={text}
|
||||||
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
||||||
>
|
>
|
||||||
<button
|
<OLIconButton
|
||||||
className="btn btn-secondary"
|
|
||||||
aria-label={text}
|
|
||||||
onClick={handleDownloadProjects}
|
onClick={handleDownloadProjects}
|
||||||
>
|
variant="secondary"
|
||||||
<Icon type="cloud-download" />
|
accessibilityLabel={text}
|
||||||
</button>
|
icon={
|
||||||
</Tooltip>
|
bsVersion({
|
||||||
|
bs5: 'download',
|
||||||
|
bs3: 'cloud-download',
|
||||||
|
}) as string
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import LeaveProjectModal from '../../../modals/leave-project-modal'
|
import LeaveProjectModal from '../../../modals/leave-project-modal'
|
||||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||||
|
@ -37,9 +37,9 @@ function LeaveProjectsButton() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!hasDeletableProjectsSelected && hasLeavableProjectsSelected && (
|
{!hasDeletableProjectsSelected && hasLeavableProjectsSelected && (
|
||||||
<Button bsStyle={null} className="btn-danger" onClick={handleOpenModal}>
|
<OLButton variant="danger" onClick={handleOpenModal}>
|
||||||
{t('leave')}
|
{t('leave')}
|
||||||
</Button>
|
</OLButton>
|
||||||
)}
|
)}
|
||||||
<LeaveProjectModal
|
<LeaveProjectModal
|
||||||
projects={selectedProjects}
|
projects={selectedProjects}
|
||||||
|
|
|
@ -1,25 +1,54 @@
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
||||||
import CopyProjectMenuItem from '../menu-items/copy-project-menu-item'
|
import CopyProjectMenuItem from '../menu-items/copy-project-menu-item'
|
||||||
import RenameProjectMenuItem from '../menu-items/rename-project-menu-item'
|
import RenameProjectMenuItem from '../menu-items/rename-project-menu-item'
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
function ProjectToolsMoreDropdownButton() {
|
function ProjectToolsMoreDropdownButton() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
<ControlledDropdown id="project-tools-more-dropdown">
|
<ControlledDropdown id="project-tools-more-dropdown">
|
||||||
<Dropdown.Toggle bsStyle={null} className="btn-secondary">
|
<BS3Dropdown.Toggle bsStyle={null} className="btn-secondary">
|
||||||
{t('more')}
|
{t('more')}
|
||||||
</Dropdown.Toggle>
|
</BS3Dropdown.Toggle>
|
||||||
<Dropdown.Menu
|
<BS3Dropdown.Menu
|
||||||
className="dropdown-menu-right"
|
className="dropdown-menu-right"
|
||||||
data-testid="project-tools-more-dropdown-menu"
|
data-testid="project-tools-more-dropdown-menu"
|
||||||
>
|
>
|
||||||
<RenameProjectMenuItem />
|
<RenameProjectMenuItem />
|
||||||
<CopyProjectMenuItem />
|
<CopyProjectMenuItem />
|
||||||
</Dropdown.Menu>
|
</BS3Dropdown.Menu>
|
||||||
</ControlledDropdown>
|
</ControlledDropdown>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<Dropdown align="end">
|
||||||
|
<DropdownToggle id="project-tools-more-dropdown" variant="secondary">
|
||||||
|
{t('more')}
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu
|
||||||
|
flip={false}
|
||||||
|
data-testid="project-tools-more-dropdown-menu"
|
||||||
|
>
|
||||||
|
<li role="none">
|
||||||
|
<RenameProjectMenuItem />
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<CopyProjectMenuItem />
|
||||||
|
</li>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { sortBy } from 'lodash'
|
import { sortBy } from 'lodash'
|
||||||
import { memo, useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { Button, Dropdown } from 'react-bootstrap'
|
import { Button, Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
||||||
import Icon from '../../../../../../shared/components/icon'
|
import Icon from '../../../../../../shared/components/icon'
|
||||||
|
@ -9,6 +9,15 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
||||||
import useTag from '../../../../hooks/use-tag'
|
import useTag from '../../../../hooks/use-tag'
|
||||||
import { addProjectsToTag, removeProjectsFromTag } from '../../../../util/api'
|
import { addProjectsToTag, removeProjectsFromTag } from '../../../../util/api'
|
||||||
import { getTagColor } from '../../../../util/tag'
|
import { getTagColor } from '../../../../util/tag'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownDivider,
|
||||||
|
DropdownHeader,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
|
||||||
function TagsDropdown() {
|
function TagsDropdown() {
|
||||||
const {
|
const {
|
||||||
|
@ -84,16 +93,17 @@ function TagsDropdown() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
<ControlledDropdown id="tags">
|
<ControlledDropdown id="tags">
|
||||||
<Dropdown.Toggle
|
<BS3Dropdown.Toggle
|
||||||
bsStyle={null}
|
bsStyle={null}
|
||||||
className="btn-secondary"
|
className="btn-secondary"
|
||||||
title={t('tags')}
|
|
||||||
aria-label={t('tags')}
|
aria-label={t('tags')}
|
||||||
>
|
>
|
||||||
<MaterialIcon type="label" style={{ verticalAlign: 'sub' }} />
|
<MaterialIcon type="label" style={{ verticalAlign: 'sub' }} />
|
||||||
</Dropdown.Toggle>
|
</BS3Dropdown.Toggle>
|
||||||
<Dropdown.Menu className="dropdown-menu-right">
|
<BS3Dropdown.Menu className="dropdown-menu-right">
|
||||||
<li className="dropdown-header" role="heading" aria-level={3}>
|
<li className="dropdown-header" role="heading" aria-level={3}>
|
||||||
{t('add_to_tag')}
|
{t('add_to_tag')}
|
||||||
</li>
|
</li>
|
||||||
|
@ -141,8 +151,68 @@ function TagsDropdown() {
|
||||||
{t('create_new_tag')}
|
{t('create_new_tag')}
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
</Dropdown.Menu>
|
</BS3Dropdown.Menu>
|
||||||
</ControlledDropdown>
|
</ControlledDropdown>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<Dropdown align="end" autoClose="outside">
|
||||||
|
<DropdownToggle
|
||||||
|
id="project-tools-more-dropdown"
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={t('tags')}
|
||||||
|
>
|
||||||
|
<MaterialIcon type="label" className="align-text-top" />
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu
|
||||||
|
flip={false}
|
||||||
|
data-testid="project-tools-more-dropdown-menu"
|
||||||
|
>
|
||||||
|
<DropdownHeader>{t('add_to_tag')}</DropdownHeader>
|
||||||
|
{sortBy(tags, tag => tag.name?.toLowerCase()).map(
|
||||||
|
(tag, index) => (
|
||||||
|
<li role="none" key={tag._id}>
|
||||||
|
<DropdownItem
|
||||||
|
onClick={e =>
|
||||||
|
containsAllSelectedProjects(tag)
|
||||||
|
? handleRemoveTagFromSelectedProjects(e, tag._id)
|
||||||
|
: handleAddTagToSelectedProjects(e, tag._id)
|
||||||
|
}
|
||||||
|
aria-label={t('add_or_remove_project_from_tag', {
|
||||||
|
tagName: tag.name,
|
||||||
|
})}
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
leadingIcon={
|
||||||
|
containsAllSelectedProjects(tag) ? (
|
||||||
|
'check'
|
||||||
|
) : (
|
||||||
|
<DropdownItem.EmptyLeadingIcon />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="badge-tag-circle align-self-center ms-0"
|
||||||
|
style={{ backgroundColor: getTagColor(tag) }}
|
||||||
|
/>
|
||||||
|
<span className="text-truncate">{tag.name}</span>
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<DropdownDivider />
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
onClick={handleOpenCreateTagModal}
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
{t('create_new_tag')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<CreateTagModal id="toolbar-create-tag-modal" />
|
<CreateTagModal id="toolbar-create-tag-modal" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
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 OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||||
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 { Project } from '../../../../../../../../types/project/dashboard/api'
|
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
function TrashProjectsButton() {
|
function TrashProjectsButton() {
|
||||||
const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
|
const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
|
||||||
|
@ -40,19 +41,23 @@ function TrashProjectsButton() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="tooltip-trash-projects"
|
id="tooltip-trash-projects"
|
||||||
description={text}
|
description={text}
|
||||||
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
||||||
>
|
>
|
||||||
<button
|
<OLIconButton
|
||||||
className="btn btn-secondary"
|
|
||||||
aria-label={text}
|
|
||||||
onClick={handleOpenModal}
|
onClick={handleOpenModal}
|
||||||
>
|
variant="secondary"
|
||||||
<Icon type="trash" />
|
accessibilityLabel={text}
|
||||||
</button>
|
icon={
|
||||||
</Tooltip>
|
bsVersion({
|
||||||
|
bs5: 'delete',
|
||||||
|
bs3: 'trash',
|
||||||
|
}) as string
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</OLTooltip>
|
||||||
<TrashProjectModal
|
<TrashProjectModal
|
||||||
projects={selectedProjects}
|
projects={selectedProjects}
|
||||||
actionHandler={handleTrashProject}
|
actionHandler={handleTrashProject}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
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 { unarchiveProject } from '../../../../util/api'
|
import { unarchiveProject } from '../../../../util/api'
|
||||||
|
@ -18,13 +18,9 @@ function UnarchiveProjectsButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<OLButton variant="secondary" onClick={handleUnarchiveProjects}>
|
||||||
bsStyle={null}
|
{t('untrash')}
|
||||||
className="btn-secondary"
|
</OLButton>
|
||||||
onClick={handleUnarchiveProjects}
|
|
||||||
>
|
|
||||||
{t('unarchive')}
|
|
||||||
</Button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
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 { untrashProject } from '../../../../util/api'
|
import { untrashProject } from '../../../../util/api'
|
||||||
|
@ -18,13 +18,9 @@ function UntrashProjectsButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<OLButton variant="secondary" onClick={handleUntrashProjects}>
|
||||||
bsStyle={null}
|
|
||||||
className="btn-secondary"
|
|
||||||
onClick={handleUntrashProjects}
|
|
||||||
>
|
|
||||||
{t('untrash')}
|
{t('untrash')}
|
||||||
</Button>
|
</OLButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { MenuItem } from 'react-bootstrap'
|
import OlDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
|
||||||
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'
|
||||||
|
@ -64,6 +64,9 @@ function CopyProjectMenuItem() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<OlDropdownMenuItem onClick={handleOpenModal} as="button" tabIndex={-1}>
|
||||||
|
{t('make_a_copy')}
|
||||||
|
</OlDropdownMenuItem>
|
||||||
<CloneProjectModal
|
<CloneProjectModal
|
||||||
show={showModal}
|
show={showModal}
|
||||||
handleHide={handleCloseModal}
|
handleHide={handleCloseModal}
|
||||||
|
@ -72,7 +75,6 @@ function CopyProjectMenuItem() {
|
||||||
projectName={selectedProjects[0].name}
|
projectName={selectedProjects[0].name}
|
||||||
projectTags={projectTags}
|
projectTags={projectTags}
|
||||||
/>
|
/>
|
||||||
<MenuItem onClick={handleOpenModal}>{t('make_a_copy')}</MenuItem>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useState } from 'react'
|
||||||
import { MenuItem } 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 useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||||
|
import OlDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
|
||||||
import RenameProjectModal from '../../../modals/rename-project-modal'
|
import RenameProjectModal from '../../../modals/rename-project-modal'
|
||||||
|
|
||||||
function RenameProjectMenuItem() {
|
function RenameProjectMenuItem() {
|
||||||
|
@ -34,7 +34,9 @@ function RenameProjectMenuItem() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={handleOpenModal}>{t('rename')}</MenuItem>
|
<OlDropdownMenuItem onClick={handleOpenModal} as="button" tabIndex={-1}>
|
||||||
|
{t('rename')}
|
||||||
|
</OlDropdownMenuItem>
|
||||||
<RenameProjectModal
|
<RenameProjectModal
|
||||||
handleCloseModal={handleCloseModal}
|
handleCloseModal={handleCloseModal}
|
||||||
showModal={showModal}
|
showModal={showModal}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { ButtonGroup, ButtonToolbar } from 'react-bootstrap'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useProjectListContext } from '../../../context/project-list-context'
|
import { useProjectListContext } from '../../../context/project-list-context'
|
||||||
import ArchiveProjectsButton from './buttons/archive-projects-button'
|
import ArchiveProjectsButton from './buttons/archive-projects-button'
|
||||||
import DownloadProjectsButton from './buttons/download-projects-button'
|
import DownloadProjectsButton from './buttons/download-projects-button'
|
||||||
|
@ -11,38 +11,44 @@ import UntrashProjectsButton from './buttons/untrash-projects-button'
|
||||||
import DeleteLeaveProjectsButton from './buttons/delete-leave-projects-button'
|
import DeleteLeaveProjectsButton from './buttons/delete-leave-projects-button'
|
||||||
import LeaveProjectsButton from './buttons/leave-projects-button'
|
import LeaveProjectsButton from './buttons/leave-projects-button'
|
||||||
import DeleteProjectsButton from './buttons/delete-projects-button'
|
import DeleteProjectsButton from './buttons/delete-projects-button'
|
||||||
|
import OlButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
|
||||||
|
import OlButtonGroup from '@/features/ui/components/ol/ol-button-group'
|
||||||
|
|
||||||
function ProjectTools() {
|
function ProjectTools() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { filter, selectedProjects } = useProjectListContext()
|
const { filter, selectedProjects } = useProjectListContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonToolbar>
|
<OlButtonToolbar aria-label={t('toolbar_selected_projects')}>
|
||||||
<ButtonGroup>
|
<OlButtonGroup
|
||||||
|
aria-label={t('toolbar_selected_projects_management_actions')}
|
||||||
|
>
|
||||||
<DownloadProjectsButton />
|
<DownloadProjectsButton />
|
||||||
{filter !== 'archived' && <ArchiveProjectsButton />}
|
{filter !== 'archived' && <ArchiveProjectsButton />}
|
||||||
{filter !== 'trashed' && <TrashProjectsButton />}
|
{filter !== 'trashed' && <TrashProjectsButton />}
|
||||||
</ButtonGroup>
|
</OlButtonGroup>
|
||||||
|
|
||||||
<ButtonGroup>
|
{(filter === 'trashed' || filter === 'archived') && (
|
||||||
|
<OlButtonGroup aria-label={t('toolbar_selected_projects_restore')}>
|
||||||
{filter === 'trashed' && <UntrashProjectsButton />}
|
{filter === 'trashed' && <UntrashProjectsButton />}
|
||||||
{filter === 'archived' && <UnarchiveProjectsButton />}
|
{filter === 'archived' && <UnarchiveProjectsButton />}
|
||||||
</ButtonGroup>
|
</OlButtonGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
<ButtonGroup>
|
|
||||||
{filter === 'trashed' && (
|
{filter === 'trashed' && (
|
||||||
<>
|
<OlButtonGroup aria-label={t('toolbar_selected_projects_remove')}>
|
||||||
<LeaveProjectsButton />
|
<LeaveProjectsButton />
|
||||||
<DeleteProjectsButton />
|
<DeleteProjectsButton />
|
||||||
<DeleteLeaveProjectsButton />
|
<DeleteLeaveProjectsButton />
|
||||||
</>
|
</OlButtonGroup>
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
{!['archived', 'trashed'].includes(filter) && <TagsDropdown />}
|
{!['archived', 'trashed'].includes(filter) && <TagsDropdown />}
|
||||||
|
|
||||||
{selectedProjects.length === 1 &&
|
{selectedProjects.length === 1 &&
|
||||||
filter !== 'archived' &&
|
filter !== 'archived' &&
|
||||||
filter !== 'trashed' && <ProjectToolsMoreDropdownButton />}
|
filter !== 'trashed' && <ProjectToolsMoreDropdownButton />}
|
||||||
</ButtonToolbar>
|
</OlButtonToolbar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,12 @@ import {
|
||||||
import useTag from '../hooks/use-tag'
|
import useTag from '../hooks/use-tag'
|
||||||
import { sortBy } from 'lodash'
|
import { sortBy } from 'lodash'
|
||||||
import { Tag } from '../../../../../app/src/Features/Tags/types'
|
import { Tag } from '../../../../../app/src/Features/Tags/types'
|
||||||
|
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
type TagsListProps = {
|
type TagsListProps = {
|
||||||
onTagClick: () => void
|
onTagClick?: () => void
|
||||||
onEditClick: () => void
|
onEditClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||||
|
@ -32,22 +34,27 @@ function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||||
|
|
||||||
const handleClick = (e: React.MouseEvent, tag: Tag) => {
|
const handleClick = (e: React.MouseEvent, tag: Tag) => {
|
||||||
handleSelectTag(e, tag._id)
|
handleSelectTag(e, tag._id)
|
||||||
onTagClick()
|
onTagClick?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={
|
||||||
<>
|
<>
|
||||||
{sortBy(tags, ['name']).map((tag, index) => (
|
{sortBy(tags, ['name']).map((tag, index) => (
|
||||||
<MenuItemButton
|
<MenuItemButton
|
||||||
key={index}
|
key={index}
|
||||||
onClick={e => handleClick(e as unknown as React.MouseEvent, tag)}
|
onClick={e =>
|
||||||
|
handleClick(e as unknown as React.MouseEvent, tag)
|
||||||
|
}
|
||||||
className="projects-types-menu-item projects-types-menu-tag-item"
|
className="projects-types-menu-item projects-types-menu-tag-item"
|
||||||
afterNode={
|
afterNode={
|
||||||
<Button
|
<Button
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
handleManageTag(e, tag._id)
|
handleManageTag(e, tag._id)
|
||||||
onEditClick()
|
onEditClick?.()
|
||||||
}}
|
}}
|
||||||
className="btn-transparent edit-btn me-2"
|
className="btn-transparent edit-btn me-2"
|
||||||
bsStyle={null}
|
bsStyle={null}
|
||||||
|
@ -66,11 +73,17 @@ function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||||
color: getTagColor(tag),
|
color: getTagColor(tag),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MaterialIcon type="label" style={{ verticalAlign: 'sub' }} />
|
<MaterialIcon
|
||||||
|
type="label"
|
||||||
|
style={{ verticalAlign: 'sub' }}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{tag.name}{' '}
|
{tag.name}{' '}
|
||||||
<span className="subdued"> ({tag.project_ids?.length})</span>
|
<span className="subdued">
|
||||||
|
{' '}
|
||||||
|
({tag.project_ids?.length})
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</MenuItemButton>
|
</MenuItemButton>
|
||||||
|
@ -79,7 +92,7 @@ function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||||
className="untagged projects-types-menu-item"
|
className="untagged projects-types-menu-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
selectTag(UNCATEGORIZED_KEY)
|
selectTag(UNCATEGORIZED_KEY)
|
||||||
onTagClick()
|
onTagClick?.()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedTagId === UNCATEGORIZED_KEY ? (
|
{selectedTagId === UNCATEGORIZED_KEY ? (
|
||||||
|
@ -93,7 +106,7 @@ function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||||
<MenuItemButton
|
<MenuItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openCreateTagModal()
|
openCreateTagModal()
|
||||||
onTagClick()
|
onTagClick?.()
|
||||||
}}
|
}}
|
||||||
className="projects-types-menu-item"
|
className="projects-types-menu-item"
|
||||||
>
|
>
|
||||||
|
@ -102,6 +115,70 @@ function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||||
<span>{t('new_tag')}</span>
|
<span>{t('new_tag')}</span>
|
||||||
</span>
|
</span>
|
||||||
</MenuItemButton>
|
</MenuItemButton>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<>
|
||||||
|
{sortBy(tags, ['name']).map((tag, index) => (
|
||||||
|
<li role="none" className="position-relative" key={index}>
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={e =>
|
||||||
|
handleClick(e as unknown as React.MouseEvent, tag)
|
||||||
|
}
|
||||||
|
leadingIcon={
|
||||||
|
<span style={{ color: getTagColor(tag) }}>
|
||||||
|
<MaterialIcon type="label" className="align-text-top" />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
trailingIcon={selectedTagId === tag._id ? 'check' : undefined}
|
||||||
|
active={selectedTagId === tag._id}
|
||||||
|
>
|
||||||
|
<span className="project-menu-item-tag-name text-truncate">
|
||||||
|
{tag.name} ({tag.project_ids?.length})
|
||||||
|
</span>
|
||||||
|
</DropdownItem>
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
className="project-menu-item-edit-btn"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleManageTag(e, tag._id)
|
||||||
|
}}
|
||||||
|
aria-label={t('edit_tag')}
|
||||||
|
>
|
||||||
|
<MaterialIcon type="edit" className="align-text-top" />
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={() => selectTag(UNCATEGORIZED_KEY)}
|
||||||
|
trailingIcon={
|
||||||
|
selectedTagId === UNCATEGORIZED_KEY ? 'check' : undefined
|
||||||
|
}
|
||||||
|
active={selectedTagId === UNCATEGORIZED_KEY}
|
||||||
|
>
|
||||||
|
{t('uncategorized')} ({untaggedProjectsCount})
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={openCreateTagModal}
|
||||||
|
leadingIcon="add"
|
||||||
|
>
|
||||||
|
{t('new_tag')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<CreateTagModal id="create-tag-modal-dropdown" disableCustomColor />
|
<CreateTagModal id="create-tag-modal-dropdown" disableCustomColor />
|
||||||
<ManageTagModal id="manage-tag-modal-dropdown" />
|
<ManageTagModal id="manage-tag-modal-dropdown" />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -16,6 +16,7 @@ function ProjectListTitle({
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
let message = t('projects')
|
let message = t('projects')
|
||||||
|
|
||||||
if (selectedTag) {
|
if (selectedTag) {
|
||||||
message = `${selectedTag.name}`
|
message = `${selectedTag.name}`
|
||||||
} else if (selectedTagId === UNCATEGORIZED_KEY) {
|
} else if (selectedTagId === UNCATEGORIZED_KEY) {
|
||||||
|
@ -39,10 +40,9 @@ function ProjectListTitle({
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('project-list-title', className)}>
|
<div className={classnames('project-list-title', className)}>{message}</div>
|
||||||
<span>{message}</span>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
|
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
type BootstrapVersionSwitcherProps = {
|
type BootstrapVersionSwitcherProps = {
|
||||||
bs3: React.ReactNode
|
bs3?: React.ReactNode
|
||||||
bs5: React.ReactNode
|
bs5?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function BootstrapVersionSwitcher({
|
function BootstrapVersionSwitcher({
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import { Button as BS5Button, Spinner } from 'react-bootstrap-5'
|
import { Button as BS5Button, Spinner } from 'react-bootstrap-5'
|
||||||
import type { ButtonProps } from '@/features/ui/components/types/button-props'
|
import type { ButtonProps } from '@/features/ui/components/types/button-props'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
@ -10,7 +11,9 @@ const sizeClasses = new Map<ButtonProps['size'], string>([
|
||||||
['large', 'btn-lg'],
|
['large', 'btn-lg'],
|
||||||
])
|
])
|
||||||
|
|
||||||
export default function Button({
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
leadingIcon,
|
leadingIcon,
|
||||||
|
@ -20,7 +23,9 @@ export default function Button({
|
||||||
trailingIcon,
|
trailingIcon,
|
||||||
variant = 'primary',
|
variant = 'primary',
|
||||||
...props
|
...props
|
||||||
}: ButtonProps) {
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const sizeClass = sizeClasses.get(size)
|
const sizeClass = sizeClasses.get(size)
|
||||||
|
@ -32,7 +37,12 @@ export default function Button({
|
||||||
const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
|
const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BS5Button className={buttonClassName} variant={variant} {...props}>
|
<BS5Button
|
||||||
|
className={buttonClassName}
|
||||||
|
variant={variant}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<span className="spinner-container">
|
<span className="spinner-container">
|
||||||
<Spinner
|
<Spinner
|
||||||
|
@ -49,13 +59,23 @@ export default function Button({
|
||||||
)}
|
)}
|
||||||
<span className="button-content" aria-hidden={isLoading}>
|
<span className="button-content" aria-hidden={isLoading}>
|
||||||
{leadingIcon && (
|
{leadingIcon && (
|
||||||
<MaterialIcon type={leadingIcon} className={materialIconClassName} />
|
<MaterialIcon
|
||||||
|
type={leadingIcon}
|
||||||
|
className={materialIconClassName}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
{trailingIcon && (
|
{trailingIcon && (
|
||||||
<MaterialIcon type={trailingIcon} className={materialIconClassName} />
|
<MaterialIcon
|
||||||
|
type={trailingIcon}
|
||||||
|
className={materialIconClassName}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</BS5Button>
|
</BS5Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = 'Button'
|
||||||
|
|
||||||
|
export default Button
|
||||||
|
|
|
@ -16,19 +16,23 @@ import type {
|
||||||
DropdownHeaderProps,
|
DropdownHeaderProps,
|
||||||
} from '@/features/ui/components/types/dropdown-menu-props'
|
} from '@/features/ui/components/types/dropdown-menu-props'
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { fixedForwardRef } from '@/utils/react'
|
||||||
|
|
||||||
export function Dropdown({ ...props }: DropdownProps) {
|
export function Dropdown({ ...props }: DropdownProps) {
|
||||||
return <BS5Dropdown {...props} />
|
return <BS5Dropdown {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DropdownItem = forwardRef<
|
function DropdownItem(
|
||||||
typeof BS5DropdownItem,
|
{
|
||||||
DropdownItemProps
|
active,
|
||||||
>(
|
children,
|
||||||
(
|
description,
|
||||||
{ active, children, description, leadingIcon, trailingIcon, ...props },
|
leadingIcon,
|
||||||
ref
|
trailingIcon,
|
||||||
) => {
|
...props
|
||||||
|
}: DropdownItemProps,
|
||||||
|
ref: React.ForwardedRef<typeof BS5DropdownItem>
|
||||||
|
) {
|
||||||
let leadingIconComponent = null
|
let leadingIconComponent = null
|
||||||
if (leadingIcon) {
|
if (leadingIcon) {
|
||||||
if (typeof leadingIcon === 'string') {
|
if (typeof leadingIcon === 'string') {
|
||||||
|
@ -60,7 +64,7 @@ export const DropdownItem = forwardRef<
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
trailingIconComponent = (
|
trailingIconComponent = (
|
||||||
<span className="dropdown-item-leading-icon" aria-hidden="true">
|
<span className="dropdown-item-trailing-icon" aria-hidden="true">
|
||||||
{trailingIcon}
|
{trailingIcon}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
@ -84,8 +88,16 @@ export const DropdownItem = forwardRef<
|
||||||
</BS5DropdownItem>
|
</BS5DropdownItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
DropdownItem.displayName = 'DropdownItem'
|
function EmptyLeadingIcon() {
|
||||||
|
return <span className="dropdown-item-leading-icon-empty" />
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, {
|
||||||
|
EmptyLeadingIcon,
|
||||||
|
})
|
||||||
|
|
||||||
|
export { ForwardReferredDropdownItem as DropdownItem }
|
||||||
|
|
||||||
export function DropdownToggle({ ...props }: DropdownToggleProps) {
|
export function DropdownToggle({ ...props }: DropdownToggleProps) {
|
||||||
return <BS5DropdownToggle {...props} />
|
return <BS5DropdownToggle {...props} />
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
|
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
|
||||||
|
|
||||||
export default function IconButton({
|
const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||||
accessibilityLabel,
|
(
|
||||||
icon,
|
{ accessibilityLabel, icon, isLoading = false, size = 'default', ...props },
|
||||||
isLoading = false,
|
ref
|
||||||
size = 'default',
|
) => {
|
||||||
...props
|
|
||||||
}: IconButtonProps) {
|
|
||||||
const iconButtonClassName = `icon-button-${size}`
|
const iconButtonClassName = `icon-button-${size}`
|
||||||
const iconSizeClassName = size === 'large' ? 'icon-large' : 'icon-small'
|
const iconSizeClassName = size === 'large' ? 'icon-large' : 'icon-small'
|
||||||
const materialIconClassName = classNames(iconSizeClassName, {
|
const materialIconClassName = classNames(iconSizeClassName, {
|
||||||
|
@ -17,12 +16,18 @@ export default function IconButton({
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className={iconButtonClassName} isLoading={isLoading} {...props}>
|
<Button
|
||||||
<MaterialIcon
|
className={iconButtonClassName}
|
||||||
accessibilityLabel={accessibilityLabel}
|
isLoading={isLoading}
|
||||||
className={materialIconClassName}
|
aria-label={accessibilityLabel}
|
||||||
type={icon}
|
{...props}
|
||||||
/>
|
ref={ref}
|
||||||
|
>
|
||||||
|
<MaterialIcon className={materialIconClassName} type={icon} />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
IconButton.displayName = 'IconButton'
|
||||||
|
|
||||||
|
export default IconButton
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { cloneElement, useEffect, forwardRef } from 'react'
|
import { cloneElement, useEffect, forwardRef } from 'react'
|
||||||
import { OverlayTrigger, Tooltip as BSTooltip } from 'react-bootstrap-5'
|
import {
|
||||||
|
OverlayTrigger,
|
||||||
|
OverlayTriggerProps,
|
||||||
|
Tooltip as BSTooltip,
|
||||||
|
TooltipProps as BSTooltipProps,
|
||||||
|
} from 'react-bootstrap-5'
|
||||||
import { callFnsInSequence } from '@/utils/functions'
|
import { callFnsInSequence } from '@/utils/functions'
|
||||||
|
|
||||||
type OverlayProps = Omit<
|
type OverlayProps = Omit<OverlayTriggerProps, 'overlay' | 'children'>
|
||||||
React.ComponentProps<typeof OverlayTrigger>,
|
|
||||||
'overlay' | 'children'
|
|
||||||
>
|
|
||||||
|
|
||||||
type UpdatingTooltipProps = {
|
type UpdatingTooltipProps = {
|
||||||
popper: {
|
popper: {
|
||||||
|
@ -34,7 +36,7 @@ export type TooltipProps = {
|
||||||
description: React.ReactNode
|
description: React.ReactNode
|
||||||
id: string
|
id: string
|
||||||
overlayProps?: OverlayProps
|
overlayProps?: OverlayProps
|
||||||
tooltipProps?: React.ComponentProps<typeof BSTooltip>
|
tooltipProps?: BSTooltipProps
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
children: React.ReactElement
|
children: React.ReactElement
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { ButtonGroup, ButtonGroupProps } from 'react-bootstrap-5'
|
||||||
|
import {
|
||||||
|
ButtonGroup as BS3ButtonGroup,
|
||||||
|
ButtonGroupProps as BS3ButtonGroupProps,
|
||||||
|
} from 'react-bootstrap'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
|
type OLButtonGroupProps = ButtonGroupProps & {
|
||||||
|
bs3Props?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
function OlButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
|
||||||
|
const bs3ButtonGroupProps: BS3ButtonGroupProps = {
|
||||||
|
children: rest.children,
|
||||||
|
className: rest.className,
|
||||||
|
vertical: rest.vertical,
|
||||||
|
...getAriaAndDataProps(rest),
|
||||||
|
...bs3Props,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<BS3ButtonGroup {...bs3ButtonGroupProps} />}
|
||||||
|
bs5={<ButtonGroup {...rest} as={as} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OlButtonGroup
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { ButtonToolbar, ButtonToolbarProps } from 'react-bootstrap-5'
|
||||||
|
import {
|
||||||
|
ButtonToolbar as BS3ButtonToolbar,
|
||||||
|
ButtonToolbarProps as BS3ButtonToolbarProps,
|
||||||
|
} from 'react-bootstrap'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
|
type OLButtonToolbarProps = ButtonToolbarProps & {
|
||||||
|
bs3Props?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
function OlButtonToolbar(props: OLButtonToolbarProps) {
|
||||||
|
const { bs3Props, ...rest } = props
|
||||||
|
|
||||||
|
const bs3ButtonToolbarProps: BS3ButtonToolbarProps = {
|
||||||
|
children: rest.children,
|
||||||
|
className: rest.className,
|
||||||
|
...getAriaAndDataProps(rest),
|
||||||
|
...bs3Props,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<BS3ButtonToolbar {...bs3ButtonToolbarProps} />}
|
||||||
|
bs5={<ButtonToolbar {...rest} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OlButtonToolbar
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { MenuItem, MenuItemProps } from 'react-bootstrap'
|
||||||
|
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
import { DropdownItemProps } from '@/features/ui/components/types/dropdown-menu-props'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
|
type OlDropdownMenuItemProps = DropdownItemProps & {
|
||||||
|
bs3Props?: MenuItemProps
|
||||||
|
}
|
||||||
|
|
||||||
|
function OlDropdownMenuItem(props: OlDropdownMenuItemProps) {
|
||||||
|
const { bs3Props, ...rest } = props
|
||||||
|
|
||||||
|
const bs3MenuItemProps: MenuItemProps = {
|
||||||
|
children: rest.children,
|
||||||
|
onClick: rest.onClick,
|
||||||
|
...bs3Props,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<MenuItem {...bs3MenuItemProps} />}
|
||||||
|
bs5={<DropdownItem {...rest} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OlDropdownMenuItem
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import { bs3ButtonProps, BS3ButtonSize } from './ol-button'
|
import { bs3ButtonProps, BS3ButtonSize } from './ol-button'
|
||||||
import { Button as BS3Button } from 'react-bootstrap'
|
import { Button as BS3Button } from 'react-bootstrap'
|
||||||
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
|
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
|
||||||
import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
|
import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
|
||||||
import Icon, { IconProps } from '@/shared/components/icon'
|
import Icon, { IconProps } from '@/shared/components/icon'
|
||||||
import IconButton from '../bootstrap-5/icon-button'
|
import IconButton from '../bootstrap-5/icon-button'
|
||||||
|
import { callFnsInSequence } from '@/utils/functions'
|
||||||
|
|
||||||
export type OLIconButtonProps = IconButtonProps & {
|
export type OLIconButtonProps = IconButtonProps & {
|
||||||
bs3Props?: {
|
bs3Props?: {
|
||||||
|
@ -11,28 +13,44 @@ export type OLIconButtonProps = IconButtonProps & {
|
||||||
fw?: IconProps['fw']
|
fw?: IconProps['fw']
|
||||||
className?: string
|
className?: string
|
||||||
bsSize?: BS3ButtonSize
|
bsSize?: BS3ButtonSize
|
||||||
|
onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onFocus?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
|
onBlur?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OLIconButton(props: OLIconButtonProps) {
|
const OLIconButton = forwardRef<HTMLButtonElement, OLIconButtonProps>(
|
||||||
|
(props, ref) => {
|
||||||
const { bs3Props, ...rest } = props
|
const { bs3Props, ...rest } = props
|
||||||
|
|
||||||
const { fw, loading, ...bs3Rest } = bs3Props || {}
|
const { fw, loading, ...bs3Rest } = bs3Props || {}
|
||||||
|
|
||||||
|
// BS3 OverlayTrigger automatically provides 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' event handlers
|
||||||
|
const bs3FinalProps = {
|
||||||
|
'aria-label': rest.accessibilityLabel,
|
||||||
|
...bs3ButtonProps(rest),
|
||||||
|
...bs3Rest,
|
||||||
|
onMouseOver: callFnsInSequence(bs3Props?.onMouseOver, rest.onMouseOver),
|
||||||
|
onMouseOut: callFnsInSequence(bs3Props?.onMouseOut, rest.onMouseOut),
|
||||||
|
onFocus: callFnsInSequence(bs3Props?.onFocus, rest.onFocus),
|
||||||
|
onBlur: callFnsInSequence(bs3Props?.onBlur, rest.onBlur),
|
||||||
|
}
|
||||||
|
|
||||||
|
// BS3 tooltip relies on the 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' props
|
||||||
|
// BS5 tooltip relies on the ref
|
||||||
return (
|
return (
|
||||||
<BootstrapVersionSwitcher
|
<BootstrapVersionSwitcher
|
||||||
bs3={
|
bs3={
|
||||||
<BS3Button {...bs3ButtonProps(rest)} {...bs3Rest}>
|
<BS3Button {...bs3FinalProps}>
|
||||||
{loading || (
|
{loading || <Icon type={rest.icon} fw={fw} />}
|
||||||
<Icon
|
|
||||||
type={rest.icon}
|
|
||||||
fw={fw}
|
|
||||||
accessibilityLabel={rest.accessibilityLabel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</BS3Button>
|
</BS3Button>
|
||||||
}
|
}
|
||||||
bs5={<IconButton {...rest} />}
|
bs5={<IconButton {...rest} ref={ref} />}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
OLIconButton.displayName = 'OLIconButton'
|
||||||
|
|
||||||
|
export default OLIconButton
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { MouseEventHandler, ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
export type ButtonProps = {
|
export type ButtonProps = {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
|
@ -11,7 +11,11 @@ export type ButtonProps = {
|
||||||
rel?: string
|
rel?: string
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
loadingLabel?: string
|
loadingLabel?: string
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>
|
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onFocus?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
|
onBlur?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
size?: 'small' | 'default' | 'large'
|
size?: 'small' | 'default' | 'large'
|
||||||
trailingIcon?: string
|
trailingIcon?: string
|
||||||
type?: 'button' | 'reset' | 'submit'
|
type?: 'button' | 'reset' | 'submit'
|
||||||
|
|
|
@ -16,6 +16,7 @@ export type DropdownProps = {
|
||||||
onSelect?: (eventKey: any, event: object) => any
|
onSelect?: (eventKey: any, event: object) => any
|
||||||
onToggle?: (show: boolean) => void
|
onToggle?: (show: boolean) => void
|
||||||
show?: boolean
|
show?: boolean
|
||||||
|
autoClose?: boolean | 'inside' | 'outside'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DropdownItemProps = PropsWithChildren<{
|
export type DropdownItemProps = PropsWithChildren<{
|
||||||
|
@ -43,6 +44,7 @@ export type DropdownToggleProps = PropsWithChildren<{
|
||||||
id?: string // necessary for assistive technologies
|
id?: string // necessary for assistive technologies
|
||||||
variant?: SplitButtonVariants
|
variant?: SplitButtonVariants
|
||||||
as?: ElementType
|
as?: ElementType
|
||||||
|
size?: 'sm' | 'lg'
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type DropdownMenuProps = PropsWithChildren<{
|
export type DropdownMenuProps = PropsWithChildren<{
|
||||||
|
@ -60,4 +62,5 @@ export type DropdownDividerProps = PropsWithChildren<{
|
||||||
|
|
||||||
export type DropdownHeaderProps = PropsWithChildren<{
|
export type DropdownHeaderProps = PropsWithChildren<{
|
||||||
as?: ElementType
|
as?: ElementType
|
||||||
|
className?: string
|
||||||
}>
|
}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export function callFnsInSequence<
|
export function callFnsInSequence<
|
||||||
Args,
|
Args extends Array<any>,
|
||||||
Fn extends ((...args: Args[]) => void) | void,
|
Fn extends ((...args: Args) => void) | void,
|
||||||
>(...fns: Fn[]) {
|
>(...fns: Fn[]) {
|
||||||
return (...args: Args[]) => fns.forEach(fn => fn?.(...args))
|
return (...args: Args) => fns.forEach(fn => fn?.(...args))
|
||||||
}
|
}
|
||||||
|
|
21
services/web/frontend/js/utils/react.ts
Normal file
21
services/web/frontend/js/utils/react.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
|
export const fixedForwardRef = <
|
||||||
|
T,
|
||||||
|
P = object,
|
||||||
|
A extends Record<string, React.FunctionComponent> = Record<
|
||||||
|
string,
|
||||||
|
React.FunctionComponent
|
||||||
|
>,
|
||||||
|
>(
|
||||||
|
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
|
||||||
|
propsToAttach: A = {} as A
|
||||||
|
): ((props: P & React.RefAttributes<T>) => React.ReactElement | null) & A => {
|
||||||
|
const ForwardReferredComponent = forwardRef(render) as any
|
||||||
|
|
||||||
|
for (const i in propsToAttach) {
|
||||||
|
ForwardReferredComponent[i] = propsToAttach[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ForwardReferredComponent
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
DropdownDivider,
|
DropdownDivider,
|
||||||
|
DropdownHeader,
|
||||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
import type { Meta } from '@storybook/react'
|
import type { Meta } from '@storybook/react'
|
||||||
|
|
||||||
|
@ -58,6 +59,35 @@ export const Active = (args: Args) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MultipleSelection = (args: Args) => {
|
||||||
|
console.log('DropdownItem.EmptyLeadingIcon', DropdownItem.EmptyLeadingIcon)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu show>
|
||||||
|
<DropdownHeader>Header</DropdownHeader>
|
||||||
|
<li>
|
||||||
|
<DropdownItem
|
||||||
|
eventKey="1"
|
||||||
|
href="#/action-1"
|
||||||
|
leadingIcon={<DropdownItem.EmptyLeadingIcon />}
|
||||||
|
>
|
||||||
|
Example
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<DropdownItem eventKey="2" href="#/action-2" leadingIcon="check">
|
||||||
|
Example
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<DropdownItem eventKey="3" href="#/action-3" leadingIcon="check">
|
||||||
|
Example
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const Danger = (args: Args) => {
|
export const Danger = (args: Args) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu show>
|
<DropdownMenu show>
|
||||||
|
|
|
@ -205,8 +205,6 @@
|
||||||
font-size: @font-size-large;
|
font-size: @font-size-large;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.project-list-filters {
|
ul.project-list-filters {
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
@import 'bootstrap-5/scss/forms';
|
@import 'bootstrap-5/scss/forms';
|
||||||
@import 'bootstrap-5/scss/buttons';
|
@import 'bootstrap-5/scss/buttons';
|
||||||
@import 'bootstrap-5/scss/dropdown';
|
@import 'bootstrap-5/scss/dropdown';
|
||||||
|
@import 'bootstrap-5/scss/button-group';
|
||||||
@import 'bootstrap-5/scss/badge';
|
@import 'bootstrap-5/scss/badge';
|
||||||
@import 'bootstrap-5/scss/modal';
|
@import 'bootstrap-5/scss/modal';
|
||||||
@import 'bootstrap-5/scss/tooltip';
|
@import 'bootstrap-5/scss/tooltip';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@import 'button';
|
@import 'button';
|
||||||
|
@import 'button-group';
|
||||||
@import 'dropdown-menu';
|
@import 'dropdown-menu';
|
||||||
@import 'split-button';
|
@import 'split-button';
|
||||||
@import 'notifications';
|
@import 'notifications';
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
.btn-group {
|
||||||
|
> .btn {
|
||||||
|
&:first-child {
|
||||||
|
padding-left: var(--spacing-05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: var(--spacing-05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toolbar {
|
||||||
|
gap: var(--spacing-03);
|
||||||
|
}
|
|
@ -221,3 +221,19 @@ a.btn:visited {
|
||||||
.copy-button {
|
.copy-button {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-reset {
|
||||||
|
@include reset-button;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-transparent {
|
||||||
|
background: none !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
color: inherit !important;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none !important;
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -104,6 +104,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-item-leading-icon,
|
||||||
|
.dropdown-item-trailing-icon {
|
||||||
|
.material-symbols {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item-leading-icon-empty {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
// description text should look disabled when the dropdown item is disabled
|
// description text should look disabled when the dropdown item is disabled
|
||||||
.dropdown-item.disabled .dropdown-item-description,
|
.dropdown-item.disabled .dropdown-item-description,
|
||||||
.dropdown-item[aria-disabled='true'] .dropdown-item-description {
|
.dropdown-item[aria-disabled='true'] .dropdown-item-description {
|
||||||
|
|
|
@ -164,6 +164,65 @@
|
||||||
padding: var(--spacing-08) var(--spacing-06);
|
padding: var(--spacing-08) var(--spacing-06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-list-header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-05);
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list-title {
|
||||||
|
min-width: 0;
|
||||||
|
color: $content-secondary;
|
||||||
|
|
||||||
|
@include heading-sm;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-tools {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
.project-tools {
|
||||||
|
float: left;
|
||||||
|
margin-left: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dropdown,
|
||||||
|
.dropdown-toggle {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-sort-dropdown {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-menu-item-edit-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: var(--spacing-09);
|
||||||
|
width: initial;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-menu-item-tag-name {
|
||||||
|
padding-right: var(--spacing-13);
|
||||||
|
}
|
||||||
|
|
||||||
ul.project-list-filters {
|
ul.project-list-filters {
|
||||||
margin: var(--spacing-05) calc(-1 * var(--spacing-06));
|
margin: var(--spacing-05) calc(-1 * var(--spacing-06));
|
||||||
|
|
||||||
|
@ -609,6 +668,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.current-plan {
|
||||||
|
a.current-plan-label {
|
||||||
|
text-decoration: none;
|
||||||
|
color: $content-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-plan-label-icon {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
color: var(--bg-info-01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* stylelint-disable selector-class-pattern */
|
/* stylelint-disable selector-class-pattern */
|
||||||
.project-list-upload-project-modal-uppy-dashboard .uppy-Root {
|
.project-list-upload-project-modal-uppy-dashboard .uppy-Root {
|
||||||
.uppy-Dashboard-AddFiles-title {
|
.uppy-Dashboard-AddFiles-title {
|
||||||
|
@ -747,7 +818,7 @@ form.project-search {
|
||||||
margin: 3px; // it's centered, no matching spacing variable
|
margin: 3px; // it's centered, no matching spacing variable
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(md) {
|
||||||
margin: 5px; // it's centered, no matching spacing variable
|
margin: 5px; // it's centered, no matching spacing variable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -757,7 +828,7 @@ form.project-search {
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(md) {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -769,7 +840,7 @@ form.project-search {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(md) {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
margin: var(--spacing-08);
|
margin: var(--spacing-08);
|
||||||
|
|
|
@ -653,6 +653,7 @@
|
||||||
"files_cannot_include_invalid_characters": "File name is empty or contains invalid characters",
|
"files_cannot_include_invalid_characters": "File name is empty or contains invalid characters",
|
||||||
"files_selected": "files selected.",
|
"files_selected": "files selected.",
|
||||||
"fill_in_our_quick_survey": "Fill in our quick survey.",
|
"fill_in_our_quick_survey": "Fill in our quick survey.",
|
||||||
|
"filter_projects": "Filter projects",
|
||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
"find_out_more": "Find out More",
|
"find_out_more": "Find out More",
|
||||||
"find_out_more_about_institution_login": "Find out more about institutional login",
|
"find_out_more_about_institution_login": "Find out more about institutional login",
|
||||||
|
@ -1863,6 +1864,7 @@
|
||||||
"sorry_your_token_expired": "Sorry, your token expired",
|
"sorry_your_token_expired": "Sorry, your token expired",
|
||||||
"sort_by": "Sort by",
|
"sort_by": "Sort by",
|
||||||
"sort_by_x": "Sort by __x__",
|
"sort_by_x": "Sort by __x__",
|
||||||
|
"sort_projects": "Sort projects",
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
"spell_check": "Spell check",
|
"spell_check": "Spell check",
|
||||||
"sso": "SSO",
|
"sso": "SSO",
|
||||||
|
@ -2117,6 +2119,10 @@
|
||||||
"toolbar_insert_table": "Insert Table",
|
"toolbar_insert_table": "Insert Table",
|
||||||
"toolbar_numbered_list": "Numbered List",
|
"toolbar_numbered_list": "Numbered List",
|
||||||
"toolbar_redo": "Redo",
|
"toolbar_redo": "Redo",
|
||||||
|
"toolbar_selected_projects": "Selected projects",
|
||||||
|
"toolbar_selected_projects_management_actions": "Selected projects management actions",
|
||||||
|
"toolbar_selected_projects_remove": "Remove selected projects",
|
||||||
|
"toolbar_selected_projects_restore": "Restore selected projects",
|
||||||
"toolbar_table_insert_size_table": "Insert __size__ table",
|
"toolbar_table_insert_size_table": "Insert __size__ table",
|
||||||
"toolbar_table_insert_table_lowercase": "Insert table",
|
"toolbar_table_insert_table_lowercase": "Insert table",
|
||||||
"toolbar_toggle_symbol_palette": "Toggle Symbol Palette",
|
"toolbar_toggle_symbol_palette": "Toggle Symbol Palette",
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('<ProjectTools />', function () {
|
||||||
screen.getByLabelText('Download')
|
screen.getByLabelText('Download')
|
||||||
screen.getByLabelText('Archive')
|
screen.getByLabelText('Archive')
|
||||||
screen.getByLabelText('Trash')
|
screen.getByLabelText('Trash')
|
||||||
screen.getByTitle('Tags')
|
screen.getByLabelText('Tags')
|
||||||
screen.getByRole('button', { name: 'Create new tag' })
|
screen.getByRole('button', { name: 'Create new tag' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue