mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #19466 from overleaf/ii-bs5-projects-tags
[web] BS5 projects tags GitOrigin-RevId: 4cdea8ad374c8b0ecba9281e3bde64517e6b0153
This commit is contained in:
parent
e60885aa88
commit
bef43020cd
13 changed files with 256 additions and 95 deletions
|
@ -29,10 +29,7 @@ describe('History', function () {
|
|||
cy.log(`download version ${JSON.stringify(name)}`)
|
||||
cy.findByText('Labels').click()
|
||||
cy.findByText(name)
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.closest('[data-testid="history-version-details"]')
|
||||
.within(() => {
|
||||
cy.get('.history-version-dropdown-menu-btn').click()
|
||||
cy.findByText('Download this version').click()
|
||||
|
|
|
@ -1,31 +1,53 @@
|
|||
import { FC } from 'react'
|
||||
import { Tag } from '../../../../../app/src/Features/Tags/types'
|
||||
import { Tag as TagType } from '../../../../../app/src/Features/Tags/types'
|
||||
import { getTagColor } from '@/features/project-list/util/tag'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tag from '@/features/ui/components/bootstrap-5/tag'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
export const CloneProjectTag: FC<{
|
||||
tag: Tag
|
||||
removeTag: (tag: Tag) => void
|
||||
tag: TagType
|
||||
removeTag: (tag: TagType) => void
|
||||
}> = ({ tag, removeTag }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="tag-label" role="option" aria-selected>
|
||||
<span className="label label-default tag-label-name">
|
||||
<span style={{ color: getTagColor(tag) }}>
|
||||
<Icon type="circle" aria-hidden />
|
||||
</span>{' '}
|
||||
{tag.name}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="label label-default tag-label-remove"
|
||||
onClick={() => removeTag(tag)}
|
||||
aria-label={t('remove_tag', { tagName: tag.name })}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<div className="tag-label" role="option" aria-selected>
|
||||
<span className="label label-default tag-label-name">
|
||||
<span style={{ color: getTagColor(tag) }}>
|
||||
<Icon type="circle" aria-hidden />
|
||||
</span>{' '}
|
||||
{tag.name}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="label label-default tag-label-remove"
|
||||
onClick={() => removeTag(tag)}
|
||||
aria-label={t('remove_tag', { tagName: tag.name })}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
bs5={
|
||||
<Tag
|
||||
prepend={
|
||||
<i
|
||||
className="badge-tag-circle"
|
||||
style={{ backgroundColor: getTagColor(tag) }}
|
||||
/>
|
||||
}
|
||||
closeBtnProps={{
|
||||
onClick: () => removeTag(tag),
|
||||
}}
|
||||
className="ms-2 mb-2"
|
||||
>
|
||||
{tag.name}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { useCallback, useState, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Tag } from '../../../../../../../app/src/Features/Tags/types'
|
||||
import { Tag as TagType } from '../../../../../../../app/src/Features/Tags/types'
|
||||
import Icon from '../../../../../shared/components/icon'
|
||||
import { useProjectListContext } from '../../../context/project-list-context'
|
||||
import { removeProjectFromTag } from '../../../util/api'
|
||||
import classnames from 'classnames'
|
||||
import { getTagColor } from '../../../util/tag'
|
||||
import Tag from '@/features/ui/components/bootstrap-5/tag'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type InlineTagsProps = {
|
||||
projectId: string
|
||||
|
@ -27,7 +29,7 @@ function InlineTags({ projectId, ...props }: InlineTagsProps) {
|
|||
}
|
||||
|
||||
type InlineTagProps = {
|
||||
tag: Tag
|
||||
tag: TagType
|
||||
projectId: string
|
||||
}
|
||||
|
||||
|
@ -57,38 +59,62 @@ function InlineTag({ tag, projectId }: InlineTagProps) {
|
|||
const handleCloseMouseOut = () => setClassNames('')
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className={classnames('tag-label', classNames)}
|
||||
onClick={handleLabelClick}
|
||||
ref={tagLabelRef}
|
||||
>
|
||||
<button
|
||||
className="label label-default tag-label-name"
|
||||
aria-label={t('select_tag', { tagName: tag.name })}
|
||||
ref={tagBtnRef}
|
||||
onClick={() => selectTag(tag._id)}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: getTagColor(tag),
|
||||
}}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className={classnames('tag-label', classNames)}
|
||||
onClick={handleLabelClick}
|
||||
ref={tagLabelRef}
|
||||
>
|
||||
<Icon type="circle" aria-hidden="true" />
|
||||
</span>{' '}
|
||||
{tag.name}
|
||||
</button>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<button
|
||||
className="label label-default tag-label-remove"
|
||||
aria-label={t('remove_tag', { tagName: tag.name })}
|
||||
onClick={() => handleRemoveTag(tag._id, projectId)}
|
||||
onMouseOver={handleCloseMouseOver}
|
||||
onMouseOut={handleCloseMouseOut}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="label label-default tag-label-name"
|
||||
aria-label={t('select_tag', { tagName: tag.name })}
|
||||
ref={tagBtnRef}
|
||||
onClick={() => selectTag(tag._id)}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: getTagColor(tag),
|
||||
}}
|
||||
>
|
||||
<Icon type="circle" aria-hidden="true" />
|
||||
</span>{' '}
|
||||
{tag.name}
|
||||
</button>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<button
|
||||
className="label label-default tag-label-remove"
|
||||
aria-label={t('remove_tag', { tagName: tag.name })}
|
||||
onClick={() => handleRemoveTag(tag._id, projectId)}
|
||||
onMouseOver={handleCloseMouseOver}
|
||||
onMouseOut={handleCloseMouseOut}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
bs5={
|
||||
<Tag
|
||||
prepend={
|
||||
<i
|
||||
className="badge-tag-circle"
|
||||
style={{ backgroundColor: getTagColor(tag) }}
|
||||
/>
|
||||
}
|
||||
contentProps={{
|
||||
'aria-label': t('select_tag', { tagName: tag.name }),
|
||||
onClick: () => selectTag(tag._id),
|
||||
}}
|
||||
closeBtnProps={{
|
||||
onClick: () => handleRemoveTag(tag._id, projectId),
|
||||
}}
|
||||
className="ms-2"
|
||||
>
|
||||
{tag.name}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Badge } from 'react-bootstrap-5'
|
||||
import { Badge, BadgeProps } from 'react-bootstrap-5'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { MergeAndOverride } from '../../../../../../types/utils'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type TagProps = MergeAndOverride<
|
||||
React.ComponentProps<typeof Badge>,
|
||||
BadgeProps,
|
||||
{
|
||||
prepend?: React.ReactNode
|
||||
contentProps?: React.ComponentProps<'button'>
|
||||
closeBtnProps?: React.ComponentProps<'button'>
|
||||
}
|
||||
>
|
||||
|
@ -15,16 +16,35 @@ type TagProps = MergeAndOverride<
|
|||
function Tag({
|
||||
prepend,
|
||||
children,
|
||||
contentProps,
|
||||
closeBtnProps,
|
||||
className,
|
||||
...rest
|
||||
}: TagProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Badge bg="light" className={classnames('badge-tag', className)} {...rest}>
|
||||
const content = (
|
||||
<>
|
||||
{prepend && <span className="badge-prepend">{prepend}</span>}
|
||||
<span className="badge-content">{children}</span>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<Badge bg="light" className={classnames('badge-tag', className)} {...rest}>
|
||||
{contentProps?.onClick ? (
|
||||
<button
|
||||
type="button"
|
||||
className="badge-tag-content badge-tag-content-btn"
|
||||
{...contentProps}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
) : (
|
||||
<span className="badge-tag-content" {...contentProps}>
|
||||
{content}
|
||||
</span>
|
||||
)}
|
||||
{closeBtnProps && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -21,7 +21,7 @@ function Badge({ prepend, children, bsStyle, className, ...rest }: BadgeProps) {
|
|||
return (
|
||||
<span className={classNames} {...rest}>
|
||||
{prepend && <span className="badge-tag-bs3-prepend">{prepend}</span>}
|
||||
{children}
|
||||
<span className="badge-tag-bs3-content">{children}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ type TagProps = MergeAndOverride<
|
|||
{
|
||||
prepend?: React.ReactNode
|
||||
children: React.ReactNode
|
||||
contentProps?: React.ComponentProps<'button'>
|
||||
closeBtnProps?: React.ComponentProps<'button'>
|
||||
className?: string
|
||||
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
|
||||
|
@ -17,6 +18,7 @@ type TagProps = MergeAndOverride<
|
|||
function Tag({
|
||||
prepend,
|
||||
children,
|
||||
contentProps,
|
||||
closeBtnProps,
|
||||
bsStyle,
|
||||
className,
|
||||
|
@ -26,8 +28,10 @@ function Tag({
|
|||
|
||||
return (
|
||||
<span className={classnames('badge-tag-bs3', className)} {...rest}>
|
||||
{prepend && <span className="badge-tag-bs3-prepend">{prepend}</span>}
|
||||
<span className="badge-tag-bs3-content">{children}</span>
|
||||
<span className="badge-tag-bs3-content-wrapper">
|
||||
{prepend && <span className="badge-tag-bs3-prepend">{prepend}</span>}
|
||||
<span className="badge-tag-bs3-content">{children}</span>
|
||||
</span>
|
||||
{closeBtnProps && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -58,3 +58,20 @@ export const TagWithCloseButton: Story = {
|
|||
)
|
||||
},
|
||||
}
|
||||
|
||||
export const TagWithContentButtonAndCloseButton: Story = {
|
||||
render: args => {
|
||||
return (
|
||||
<Tag
|
||||
prepend={<Icon type="tag" fw />}
|
||||
contentProps={{
|
||||
onClick: () => alert('Content button clicked!'),
|
||||
}}
|
||||
closeBtnProps={{
|
||||
onClick: () => alert('Close triggered!'),
|
||||
}}
|
||||
{...args}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ $table-striped-bg: var(--bg-light-secondary);
|
|||
$table-active-color: $table-color;
|
||||
$table-active-bg: $bg-accent-03;
|
||||
$table-hover-color: $table-color;
|
||||
$table-hover-bg: var(--bg-light-tertiary);
|
||||
$table-hover-bg: var(--bg-light-secondary);
|
||||
$table-border-color: $border-divider;
|
||||
$table-striped-order: even;
|
||||
$table-caption-color: var(--content-secondary);
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
$max-width: 160px;
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
max-width: 100%;
|
||||
line-height: var(--line-height-01);
|
||||
padding: 0 var(--bs-badge-padding-x);
|
||||
|
||||
&:not(.badge-tag) {
|
||||
max-width: $max-width;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-prepend {
|
||||
margin-left: calc($spacing-01 / -2);
|
||||
margin-right: $spacing-01;
|
||||
margin-right: var(--spacing-02);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -20,8 +25,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
padding: 0 $spacing-02;
|
||||
margin: 0 (-$spacing-02) 0 $spacing-02;
|
||||
padding: 0 var(--spacing-02);
|
||||
border-top-right-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
color: inherit;
|
||||
|
@ -30,7 +34,10 @@
|
|||
.badge-close-icon {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-close,
|
||||
.badge-tag-content-btn {
|
||||
&:hover {
|
||||
background-color: var(--neutral-40);
|
||||
}
|
||||
|
@ -45,9 +52,36 @@
|
|||
.badge-tag {
|
||||
@include body-sm;
|
||||
|
||||
padding: 0;
|
||||
color: $dark;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--neutral-30) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-tag-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
max-width: $max-width;
|
||||
padding-left: var(--bs-badge-padding-x);
|
||||
padding-right: var(--bs-badge-padding-x);
|
||||
border-top-left-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
.badge-tag-content-btn {
|
||||
@include reset-button;
|
||||
|
||||
padding-left: var(--bs-badge-padding-x);
|
||||
padding-right: var(--bs-badge-padding-x);
|
||||
}
|
||||
|
||||
.badge-tag-circle {
|
||||
display: block;
|
||||
margin: var(--spacing-02);
|
||||
width: var(--spacing-04);
|
||||
height: var(--spacing-04);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
|
@ -47,4 +47,10 @@
|
|||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
> tbody > tr:nth-of-type(#{$table-striped-order}) {
|
||||
&:hover > * {
|
||||
--#{$prefix}table-hover-bg: var(--bg-light-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
display: flex;
|
||||
height: 100%;
|
||||
flex-flow: column nowrap;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.row:first-child {
|
||||
flex-grow: 1; /* fill vertical space so notifications are pushed to bottom */
|
||||
|
@ -51,8 +50,6 @@
|
|||
flex-grow: 1;
|
||||
padding: var(--spacing-08) var(--spacing-06);
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
padding-top: var(--spacing-08);
|
||||
padding-bottom: var(--spacing-08);
|
||||
color: var(--neutral-40);
|
||||
|
||||
.small {
|
||||
|
@ -331,27 +328,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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%;
|
||||
|
@ -428,6 +404,18 @@
|
|||
@include text-truncate;
|
||||
}
|
||||
|
||||
.dash-cell-tag,
|
||||
.clone-project-tag {
|
||||
.badge-tag {
|
||||
margin-top: var(--spacing-06);
|
||||
margin-bottom: var(--spacing-06);
|
||||
|
||||
&:first-child {
|
||||
margin-left: initial !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.dash-cell-checkbox {
|
||||
width: 4%;
|
||||
|
@ -554,6 +542,15 @@
|
|||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
td {
|
||||
padding-top: var(--spacing-02);
|
||||
padding-bottom: var(--spacing-02);
|
||||
}
|
||||
|
||||
td:not(.dash-cell-actions) {
|
||||
padding-right: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.dash-cell-name,
|
||||
|
@ -660,6 +657,14 @@
|
|||
margin-bottom: var(--spacing-05);
|
||||
}
|
||||
|
||||
.clone-project-tag {
|
||||
display: flex;
|
||||
|
||||
.form-label {
|
||||
margin-top: var(--spacing-01);
|
||||
}
|
||||
}
|
||||
|
||||
form.project-search {
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
|
@ -670,6 +675,27 @@ form.project-search {
|
|||
@include reset-button;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-item {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
height: @size;
|
||||
max-width: 100%;
|
||||
min-height: @size;
|
||||
padding: 0 @padding;
|
||||
white-space: nowrap;
|
||||
color: @ol-blue-gray-6;
|
||||
background-color: @neutral-20;
|
||||
|
@ -16,6 +15,16 @@
|
|||
background-color: @neutral-30;
|
||||
}
|
||||
|
||||
&-content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding-left: @padding;
|
||||
padding-right: @padding;
|
||||
border-top-left-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
&-prepend {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
@ -23,7 +32,6 @@
|
|||
&-close {
|
||||
.reset-button;
|
||||
width: @size;
|
||||
margin-left: 4px;
|
||||
font-size: 20px;
|
||||
height: @size;
|
||||
line-height: 1;
|
||||
|
@ -31,7 +39,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: -@padding;
|
||||
padding: 0 @padding;
|
||||
border-top-right-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
color: inherit;
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
padding: 0.3em 0.6em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// Colors
|
||||
|
|
Loading…
Reference in a new issue