Merge pull request #19466 from overleaf/ii-bs5-projects-tags

[web] BS5 projects tags

GitOrigin-RevId: 4cdea8ad374c8b0ecba9281e3bde64517e6b0153
This commit is contained in:
ilkin-overleaf 2024-07-24 16:41:40 +03:00 committed by Copybot
parent e60885aa88
commit bef43020cd
13 changed files with 256 additions and 95 deletions

View file

@ -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()

View file

@ -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>
}
/>
)
}

View file

@ -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>
}
/>
)
}

View file

@ -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"

View file

@ -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>
)
}

View file

@ -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"

View file

@ -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}
/>
)
},
}

View file

@ -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);

View file

@ -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%;
}

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -40,6 +40,7 @@
padding: 0.3em 0.6em;
display: inline-flex;
align-items: center;
max-width: 100%;
}
// Colors