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:
Rebeka Dekany
2024-07-17 12:00:52 +02:00
committed by Copybot
parent 51d7d8df84
commit 331e652fe2
11 changed files with 525 additions and 187 deletions
@@ -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>
)
}
@@ -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 && (
<>
@@ -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')} &hellip;</> : t('create')}
</Button>
</Modal.Footer>
</AccessibleModal>
{t('create')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}
@@ -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')} &hellip;</> : t('delete')}
</Button>
</Modal.Footer>
</AccessibleModal>
{t('delete')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}
@@ -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')} &hellip;</> : t('save')}
</Button>
</Modal.Footer>
</AccessibleModal>
{t('save')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}
@@ -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;
}
}