mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-02 05:41:33 +02:00
Merge pull request #19370 from overleaf/rd-dashboard-left-sidebar
[web] Migrate the left menu on the project dashboard part #1 GitOrigin-RevId: e22685e521bd7e56e426940cb56331d86d20cada
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useProjectListContext } from '../context/project-list-context'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import classNames from 'classnames'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export function useAddAffiliation() {
|
||||
const { totalProjectsCount } = useProjectListContext()
|
||||
@@ -26,18 +26,14 @@ function AddAffiliation({ className }: AddAffiliationProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
const classes = classNames('text-centered', 'add-affiliation', className)
|
||||
const classes = classNames('text-center', 'add-affiliation', className)
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<p>{t('are_you_affiliated_with_an_institution')}</p>
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary-info btn-secondary"
|
||||
href="/user/settings"
|
||||
>
|
||||
<OLButton variant="secondary" href="/user/settings">
|
||||
{t('add_affiliation')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+19
-13
@@ -1,9 +1,9 @@
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import useSelectColor from '../../hooks/use-select-color'
|
||||
import { SketchPicker } from 'react-color'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '../../../../shared/components/tooltip'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
const PRESET_COLORS: ReadonlyArray<{ color: string; name: string }> = [
|
||||
{ color: '#A7B1C2', name: 'Grey' },
|
||||
@@ -48,7 +48,7 @@ function ColorPickerItem({ color, name }: ColorPickerItemProps) {
|
||||
{t('select_color', { name })}
|
||||
</span>
|
||||
{!pickingCustomColor && color === selectedColor && (
|
||||
<Icon type="check" className="color-picker-item-icon" />
|
||||
<MaterialIcon type="check" className="color-picker-item-icon" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -95,19 +95,25 @@ function MoreButton() {
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
key="tooltip-color-picker-plus"
|
||||
id="tooltip-color-picker-plus"
|
||||
description={t('choose_a_custom_color')}
|
||||
overlayProps={{ delay: 0, placement: 'bottom' }}
|
||||
overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
|
||||
>
|
||||
{isCustomColorSelected ? (
|
||||
<Icon type="check" className="color-picker-item-icon" />
|
||||
) : showCustomPicker ? (
|
||||
<Icon type="chevron-down" className="color-picker-more-open" />
|
||||
) : (
|
||||
<Icon type="plus" className="color-picker-more" />
|
||||
)}
|
||||
</Tooltip>
|
||||
<div>
|
||||
{isCustomColorSelected ? (
|
||||
<MaterialIcon type="check" className="color-picker-item-icon" />
|
||||
) : showCustomPicker ? (
|
||||
<MaterialIcon
|
||||
type="expand_more"
|
||||
className="color-picker-more-open"
|
||||
/>
|
||||
) : (
|
||||
<MaterialIcon type="add" className="color-picker-more" />
|
||||
)}
|
||||
</div>
|
||||
</OLTooltip>
|
||||
</div>
|
||||
{showCustomPicker && (
|
||||
<>
|
||||
|
||||
+51
-38
@@ -1,8 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Button, ControlLabel, Form, FormGroup, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
||||
@@ -11,6 +9,18 @@ import { createTag } from '../../util/api'
|
||||
import { MAX_TAG_LENGTH } from '../../util/tag'
|
||||
import { ColorPicker } from '../color-picker/color-picker'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
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 OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
||||
import OLForm from '@/features/ui/components/ol/ol-form'
|
||||
|
||||
type CreateTagModalProps = {
|
||||
id: string
|
||||
@@ -30,7 +40,7 @@ export default function CreateTagModal({
|
||||
const { tags } = useProjectListContext()
|
||||
const { selectedColor } = useSelectColor()
|
||||
const { t } = useTranslation()
|
||||
const { isError, runAsync, status } = useAsync<Tag>()
|
||||
const { isLoading, isError, runAsync, status } = useAsync<Tag>()
|
||||
const { autoFocusedRef } = useRefWithAutoFocus<HTMLInputElement>()
|
||||
|
||||
const [tagName, setTagName] = useState<string>()
|
||||
@@ -69,60 +79,63 @@ export default function CreateTagModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal show animation onHide={onClose} id={id} backdrop="static">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('create_new_tag')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModal show animation onHide={onClose} id={id} backdrop="static">
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('create_new_tag')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<Form name="createTagForm" onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
<label htmlFor="new-tag-form-name">{t('new_tag_name')}</label>
|
||||
<input
|
||||
className="form-control"
|
||||
id="new-tag-form-name"
|
||||
<OLModalBody>
|
||||
<OLForm id="create-tag-modal-form" onSubmit={handleSubmit}>
|
||||
<OLFormGroup controlId="create-tag-modal-form">
|
||||
<OLFormLabel>{t('new_tag_name')}</OLFormLabel>
|
||||
<OLFormControl
|
||||
name="new-tag-form-name"
|
||||
onChange={e => setTagName(e.target.value)}
|
||||
ref={autoFocusedRef}
|
||||
required
|
||||
type="text"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup aria-hidden="true">
|
||||
<ControlLabel>{t('tag_color')}</ControlLabel>:{' '}
|
||||
</OLFormGroup>
|
||||
<OLFormGroup aria-hidden="true">
|
||||
<OLFormLabel>{t('tag_color')}</OLFormLabel>:{' '}
|
||||
<div>
|
||||
<ColorPicker disableCustomColor={disableCustomColor} />
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
</OLFormGroup>
|
||||
</OLForm>
|
||||
{validationError && (
|
||||
<div className="modal-footer-left">
|
||||
<span className="text-danger error">{validationError}</span>
|
||||
</div>
|
||||
<OLNotification type="error" content={validationError} />
|
||||
)}
|
||||
{isError && (
|
||||
<div className="modal-footer-left">
|
||||
<span className="text-danger error">
|
||||
{t('generic_something_went_wrong')}
|
||||
</span>
|
||||
</div>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={t('generic_something_went_wrong')}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={onClose} disabled={status === 'pending'}>
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
disabled={status === 'pending'}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
</OLButton>
|
||||
<OLButton
|
||||
onClick={() => runCreateTag()}
|
||||
bsStyle="primary"
|
||||
variant="primary"
|
||||
disabled={
|
||||
status === 'pending' || !tagName?.length || !!validationError
|
||||
}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('creating')}…` : t('create'),
|
||||
}}
|
||||
>
|
||||
{status === 'pending' ? <>{t('creating')} …</> : t('create')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
{t('create')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
+32
-28
@@ -1,11 +1,17 @@
|
||||
import { useCallback } from 'react'
|
||||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { deleteTag } from '../../util/api'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
type DeleteTagModalProps = {
|
||||
id: string
|
||||
@@ -39,42 +45,40 @@ export default function DeleteTagModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal show animation onHide={onClose} id={id} backdrop="static">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('delete_tag')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModal show animation onHide={onClose} id={id} backdrop="static">
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('delete_tag')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
{t('about_to_delete_tag')}
|
||||
<ul>
|
||||
<li>{tag.name}</li>
|
||||
</ul>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
{isError && (
|
||||
<div className="modal-footer-left">
|
||||
<span className="text-danger error">
|
||||
{t('generic_something_went_wrong')}
|
||||
</span>
|
||||
</div>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={t('generic_something_went_wrong')}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" onClick={onClose} disabled={isLoading}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
</OLButton>
|
||||
<OLButton
|
||||
onClick={() => runDeleteTag(tag._id)}
|
||||
bsStyle="danger"
|
||||
variant="danger"
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('deleting')}…` : t('delete'),
|
||||
}}
|
||||
>
|
||||
{isLoading ? <>{t('deleting')} …</> : t('delete')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
{t('delete')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
+43
-38
@@ -1,8 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Button, ControlLabel, Form, FormGroup, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
||||
@@ -11,6 +9,17 @@ import { editTag } from '../../util/api'
|
||||
import { getTagColor, MAX_TAG_LENGTH } from '../../util/tag'
|
||||
import { ColorPicker } from '../color-picker/color-picker'
|
||||
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 OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
type EditTagModalProps = {
|
||||
id: string
|
||||
@@ -77,14 +86,14 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal show animation onHide={onClose} id={id} backdrop="static">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('edit_tag')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModal show animation onHide={onClose} id={id} backdrop="static">
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('edit_tag')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<Form name="renameTagForm" onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
<OLModalBody>
|
||||
<OLForm onSubmit={handleSubmit}>
|
||||
<OLFormGroup>
|
||||
<input
|
||||
ref={autoFocusedRef}
|
||||
className="form-control"
|
||||
@@ -95,40 +104,32 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
|
||||
required
|
||||
onChange={e => setNewTagName(e.target.value)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup aria-hidden="true">
|
||||
<ControlLabel>{t('tag_color')}</ControlLabel>:{' '}
|
||||
</OLFormGroup>
|
||||
<OLFormGroup aria-hidden="true">
|
||||
<OLFormLabel>{t('tag_color')}</OLFormLabel>:{' '}
|
||||
<div>
|
||||
<ColorPicker />
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
</OLFormGroup>
|
||||
</OLForm>
|
||||
{validationError && (
|
||||
<div className="modal-footer-left">
|
||||
<span className="text-danger error">{validationError}</span>
|
||||
</div>
|
||||
<OLNotification type="error" content={validationError} />
|
||||
)}
|
||||
{isError && (
|
||||
<div className="modal-footer-left">
|
||||
<span className="text-danger error">
|
||||
{t('generic_something_went_wrong')}
|
||||
</span>
|
||||
</div>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={t('generic_something_went_wrong')}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" onClick={onClose} disabled={isLoading}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
</OLButton>
|
||||
<OLButton
|
||||
onClick={() => runEditTag(tag._id)}
|
||||
bsStyle="primary"
|
||||
variant="primary"
|
||||
disabled={
|
||||
isLoading ||
|
||||
status === 'pending' ||
|
||||
@@ -136,10 +137,14 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
|
||||
(newTagName === tag?.name && selectedColor === getTagColor(tag)) ||
|
||||
!!validationError
|
||||
}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('saving')}…` : t('save'),
|
||||
}}
|
||||
>
|
||||
{isLoading ? <>{t('saving')} …</> : t('save')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
{t('save')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import {
|
||||
Filter,
|
||||
useProjectListContext,
|
||||
@@ -19,9 +18,9 @@ export function SidebarFilter({ filter, text }: SidebarFilterProps) {
|
||||
<ProjectsFilterMenu filter={filter}>
|
||||
{isActive => (
|
||||
<li className={isActive ? 'active' : ''}>
|
||||
<Button onClick={() => selectFilter(filter)} bsStyle={null}>
|
||||
<button type="button" onClick={() => selectFilter(filter)}>
|
||||
{text}
|
||||
</Button>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</ProjectsFilterMenu>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { sortBy } from 'lodash'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import MaterialIcon from '../../../../shared/components/material-icon'
|
||||
import {
|
||||
UNCATEGORIZED_KEY,
|
||||
@@ -9,6 +7,13 @@ import {
|
||||
} from '../../context/project-list-context'
|
||||
import useTag from '../../hooks/use-tag'
|
||||
import { getTagColor } from '../../util/tag'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
export default function TagsList() {
|
||||
const { t } = useTranslation()
|
||||
@@ -35,14 +40,10 @@ export default function TagsList() {
|
||||
<h2>{t('organize_projects')}</h2>
|
||||
</li>
|
||||
<li className="tag">
|
||||
<Button
|
||||
className="tag-name"
|
||||
onClick={openCreateTagModal}
|
||||
bsStyle={null}
|
||||
>
|
||||
<Icon type="plus" />
|
||||
<button type="button" className="tag-name" onClick={openCreateTagModal}>
|
||||
<MaterialIcon type="add" className="tag-list-icon" />
|
||||
<span className="name">{t('new_tag')}</span>
|
||||
</Button>
|
||||
</button>
|
||||
</li>
|
||||
{sortBy(tags, tag => tag.name?.toLowerCase()).map(tag => {
|
||||
return (
|
||||
@@ -50,19 +51,19 @@ export default function TagsList() {
|
||||
className={`tag ${selectedTagId === tag._id ? 'active' : ''}`}
|
||||
key={tag._id}
|
||||
>
|
||||
<Button
|
||||
<button
|
||||
type="button"
|
||||
className="tag-name"
|
||||
onClick={e =>
|
||||
handleSelectTag(e as unknown as React.MouseEvent, tag._id)
|
||||
}
|
||||
bsStyle={null}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: getTagColor(tag),
|
||||
}}
|
||||
>
|
||||
<MaterialIcon type="label" style={{ verticalAlign: 'sub' }} />
|
||||
<MaterialIcon type="label" className="tag-list-icon" />
|
||||
</span>
|
||||
<span className="name">
|
||||
{tag.name}{' '}
|
||||
@@ -70,36 +71,66 @@ export default function TagsList() {
|
||||
({projectsPerTag[tag._id].length})
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
<span className="dropdown tag-menu">
|
||||
<button
|
||||
className="dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
dropdown-toggle=""
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li>
|
||||
<Button
|
||||
onClick={e => handleEditTag(e, tag._id)}
|
||||
className="tag-action"
|
||||
</button>
|
||||
<BootstrapVersionSwitcher
|
||||
bs5={
|
||||
<Dropdown align="end" className="tag-menu">
|
||||
<DropdownToggle id={`${tag._id}-dropdown-toggle`}>
|
||||
<span className="caret" />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu className="sm">
|
||||
<DropdownItem
|
||||
as="li"
|
||||
className="tag-action"
|
||||
onClick={e => handleEditTag(e, tag._id)}
|
||||
>
|
||||
{t('edit')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
as="li"
|
||||
className="tag-action"
|
||||
onClick={e => handleDeleteTag(e, tag._id)}
|
||||
>
|
||||
{t('delete')}
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
bs3={
|
||||
<span className="dropdown tag-menu">
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
dropdown-toggle=""
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{t('edit')}
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
onClick={e => handleDeleteTag(e, tag._id)}
|
||||
className="tag-action"
|
||||
>
|
||||
{t('delete')}
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
onClick={e => handleEditTag(e, tag._id)}
|
||||
className="tag-action"
|
||||
>
|
||||
{t('edit')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
onClick={e => handleDeleteTag(e, tag._id)}
|
||||
className="tag-action"
|
||||
>
|
||||
{t('delete')}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@@ -109,16 +140,16 @@ export default function TagsList() {
|
||||
selectedTagId === UNCATEGORIZED_KEY ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
<button
|
||||
type="button"
|
||||
className="tag-name"
|
||||
onClick={() => selectTag(UNCATEGORIZED_KEY)}
|
||||
bsStyle={null}
|
||||
>
|
||||
<span className="name">
|
||||
{t('uncategorized')}{' '}
|
||||
<span className="subdued">({untaggedProjectsCount})</span>
|
||||
</span>
|
||||
</Button>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
<CreateTagModal id="create-tag-modal" />
|
||||
|
||||
@@ -12,6 +12,7 @@ export type DropdownProps = {
|
||||
| { xxl: 'start' | 'end' }
|
||||
as?: ElementType
|
||||
children: ReactNode
|
||||
className?: string
|
||||
onSelect?: (eventKey: any, event: object) => any
|
||||
onToggle?: (show: boolean) => void
|
||||
show?: boolean
|
||||
|
||||
@@ -941,26 +941,29 @@
|
||||
|
||||
.color-picker-more {
|
||||
color: @gray-dark;
|
||||
margin: 6px 0 0 7px;
|
||||
margin: 3px;
|
||||
font-weight: bold;
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
margin: 8px 0 0 9px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-more-open {
|
||||
color: @gray-dark;
|
||||
margin: 5px 0 0 5px;
|
||||
margin: 3px;
|
||||
font-weight: bold;
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
margin: 7px 0 0 7px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-item-icon {
|
||||
margin: 6px 0 0 6px;
|
||||
margin: 4px;
|
||||
color: @white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
@@ -969,7 +972,7 @@
|
||||
margin: 24px 24px;
|
||||
|
||||
.color-picker-item-icon {
|
||||
margin: 8px 0 0 8px;
|
||||
margin: 6px;
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
.dropdown-header {
|
||||
@include body-xs;
|
||||
|
||||
padding: var(--spacing-05) var(--spacing-06) var(--spacing-02)
|
||||
var(--spacing-04);
|
||||
}
|
||||
@@ -12,16 +13,20 @@
|
||||
@include shadow-md;
|
||||
|
||||
min-width: 240px;
|
||||
|
||||
&.sm {
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
@include body-sm;
|
||||
|
||||
--bs-dropdown-item-border-radius: var(--border-radius-base);
|
||||
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: start;
|
||||
align-content: center;
|
||||
place-content: center start;
|
||||
min-height: 44px; // a minimum height of 44px to be accessible for touch screens
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
.project-list-empty-col {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
flex-flow: column nowrap;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.row:first-child {
|
||||
flex-grow: 1; /* fill vertical space so notifications are pushed to bottom */
|
||||
}
|
||||
|
||||
.card-body {
|
||||
// h2 + .card-thin top padding
|
||||
padding-bottom: calc(var(--line-height-03) + var(--line-height-03) / 2);
|
||||
@@ -47,8 +49,7 @@
|
||||
|
||||
.project-list-sidebar-react {
|
||||
flex-grow: 1;
|
||||
padding-left: var(--spacing-06);
|
||||
padding-right: var(--spacing-06);
|
||||
padding: var(--spacing-08) var(--spacing-06);
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
padding-top: var(--spacing-08);
|
||||
padding-bottom: var(--spacing-08);
|
||||
@@ -61,6 +62,7 @@
|
||||
button {
|
||||
white-space: normal;
|
||||
word-wrap: anywhere;
|
||||
|
||||
// prevents buttons from expanding sidebar width
|
||||
}
|
||||
|
||||
@@ -81,7 +83,8 @@
|
||||
|
||||
.welcome-new-wrapper {
|
||||
.welcome-title {
|
||||
@include heading-xl();
|
||||
@include heading-xl;
|
||||
|
||||
margin-top: var(--spacing-08);
|
||||
}
|
||||
|
||||
@@ -160,6 +163,196 @@
|
||||
padding: var(--spacing-08) var(--spacing-06);
|
||||
}
|
||||
|
||||
ul.project-list-filters {
|
||||
margin: var(--spacing-05) calc(-1 * var(--spacing-06));
|
||||
|
||||
.subdued {
|
||||
color: var(--content-disabled);
|
||||
}
|
||||
|
||||
> li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
> button {
|
||||
width: 100%;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
color: var(--white);
|
||||
background-color: transparent;
|
||||
border-radius: unset;
|
||||
border: none;
|
||||
border-bottom: solid 1px transparent;
|
||||
padding: var(--spacing-03) var(--spacing-06);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--neutral-70);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.separator {
|
||||
padding: var(--spacing-03) var(--spacing-06);
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> li.active {
|
||||
border-radius: 0;
|
||||
|
||||
> button {
|
||||
background-color: var(--neutral-90);
|
||||
font-weight: 700;
|
||||
color: var(--white);
|
||||
|
||||
.subdued {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-02);
|
||||
margin-bottom: var(--spacing-00);
|
||||
color: var(--content-disabled);
|
||||
text-transform: uppercase;
|
||||
padding: var(--spacing-03) var(--spacing-00);
|
||||
}
|
||||
|
||||
> li.tag {
|
||||
&.active {
|
||||
.tag-menu > button {
|
||||
color: var(--white);
|
||||
border-color: var(--white);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--neutral-90);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.untagged {
|
||||
button.tag-name {
|
||||
span.name {
|
||||
font-style: italic;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:not(.active) {
|
||||
background-color: var(--neutral-70);
|
||||
}
|
||||
|
||||
.tag-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
.tag-menu > a:hover {
|
||||
background-color: var(--neutral-90);
|
||||
}
|
||||
}
|
||||
|
||||
button.tag-name {
|
||||
position: relative;
|
||||
padding: var(--spacing-03) var(--spacing-09) var(--spacing-03)
|
||||
var(--spacing-06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
word-wrap: anywhere;
|
||||
|
||||
.tag-list-icon {
|
||||
vertical-align: sub;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.name {
|
||||
padding-left: 0.5em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-menu {
|
||||
button.dropdown-toggle {
|
||||
border: 1px solid var(--white);
|
||||
border-radius: var(--border-radius-base);
|
||||
background-color: transparent;
|
||||
color: var(--white);
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
padding: var(--spacing-01) var(--spacing-03);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
display: none;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -8px; // Half the element height.
|
||||
right: 4px;
|
||||
|
||||
&.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
button.tag-action {
|
||||
border-radius: unset;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--neutral-70);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
|
||||
&:hover {
|
||||
color: var(--white);
|
||||
background-color: var(--bg-accent-01);
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-affiliation-mobile-wrapper {
|
||||
padding: var(--spacing-07) 0;
|
||||
}
|
||||
|
||||
.add-affiliation {
|
||||
.progress {
|
||||
height: var(--spacing-05);
|
||||
margin-bottom: var(--spacing-03);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: var(--spacing-03);
|
||||
}
|
||||
|
||||
&.is-mobile p {
|
||||
@include body-xs;
|
||||
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.project-dash-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
@@ -231,7 +424,8 @@
|
||||
|
||||
.dash-cell-date-owner {
|
||||
font-size: $font-size-sm;
|
||||
@include text-truncate();
|
||||
|
||||
@include text-truncate;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
@@ -417,7 +611,7 @@
|
||||
background-color: var(--bg-dark-tertiary);
|
||||
border-color: transparent;
|
||||
color: var(--neutral-20);
|
||||
box-shadow: 2px 4px 6px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 2px 4px 6px rgb(0 0 0 / 25%);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
@@ -426,8 +620,8 @@
|
||||
|
||||
button.close {
|
||||
@extend .text-white;
|
||||
|
||||
padding: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,3 +668,84 @@ form.project-search {
|
||||
.project-search-clear-btn {
|
||||
@include reset-button;
|
||||
}
|
||||
|
||||
.color-picker-item {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
outline: none;
|
||||
border-radius: var(--border-radius-base);
|
||||
margin: 0 var(--spacing-06) 0 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow:
|
||||
0 0 0 2px var(--white),
|
||||
0 0 0 3px var(--blue-50),
|
||||
0 0 0 5px var(--blue-30);
|
||||
}
|
||||
|
||||
&.more-button {
|
||||
border: 1px solid var(--neutral-70);
|
||||
|
||||
.color-picker-more {
|
||||
color: var(--neutral-70);
|
||||
margin: 3px; // it's centered, no matching spacing variable
|
||||
font-weight: bold;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
margin: 5px; // it's centered, no matching spacing variable
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-more-open {
|
||||
color: var(--neutral-70);
|
||||
margin: 3px;
|
||||
font-weight: bold;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-item-icon {
|
||||
margin: 3px; // it's centered, no matching spacing variable
|
||||
color: var(--white);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin: var(--spacing-08);
|
||||
|
||||
.color-picker-item-icon {
|
||||
margin: 5px; // it's centered, no matching spacing variable
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-more-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.custom-picker {
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
top: 56px;
|
||||
left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user