Merge pull request #18338 from overleaf/ii-bs5-split-badges-and-tags

[web] Split badges and tags

GitOrigin-RevId: fce5a93672f431ff74c2b63a67e249f5f7e7fecd
This commit is contained in:
ilkin-overleaf 2024-05-27 15:46:48 +03:00 committed by Copybot
parent 3a1560894a
commit fe7de51827
15 changed files with 311 additions and 140 deletions

View file

@ -48,7 +48,7 @@ export default function MemberRow({
>
<Badge
bsStyle={null}
className="badge-bs3"
className="badge-tag-bs3"
aria-label={t('pending_invite')}
data-testid="badge-pending-invite"
>

View file

@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'
import { Modal } from 'react-bootstrap'
import Icon from '../../../../shared/components/icon'
import Tooltip from '../../../../shared/components/tooltip'
import Badge from '../../../../shared/components/badge'
import AccessibleModal from '../../../../shared/components/accessible-modal'
import ModalError from './modal-error'
import useAbortController from '../../../../shared/hooks/use-abort-controller'
@ -16,13 +15,14 @@ import { LoadedLabel } from '../../services/types/label'
import { debugConsole } from '@/utils/debugging'
import { formatTimeBasedOnYear } from '@/features/utils/format-date'
import { useEditorContext } from '@/shared/context/editor-context'
import Tag from '@/shared/components/tag'
type TagProps = {
label: LoadedLabel
currentUserId: string
}
function Tag({ label, currentUserId, ...props }: TagProps) {
function ChangeTag({ label, currentUserId, ...props }: TagProps) {
const { isProjectOwner } = useEditorContext()
const { t } = useTranslation()
@ -70,22 +70,21 @@ function Tag({ label, currentUserId, ...props }: TagProps) {
return (
<>
<Badge
<Tag
prepend={<Icon type="tag" fw />}
closeBtnProps={
showCloseButton
? { 'aria-label': t('delete'), onClick: showConfirmationModal }
: undefined
}
bsStyle={null}
className="badge-bs3 history-version-badge"
className="history-version-badge"
data-testid="history-version-badge"
{...props}
>
{isPseudoCurrentStateLabel
? t('history_label_project_current_state')
: label.comment}
</Badge>
</Tag>
{!isPseudoCurrentStateLabel && (
<AccessibleModal
show={showDeleteModal}
@ -167,10 +166,10 @@ function TagTooltip({ label, currentUserId, showTooltip }: LabelBadgesProps) {
id={label.id}
overlayProps={{ placement: 'left' }}
>
<Tag label={label} currentUserId={currentUserId} />
<ChangeTag label={label} currentUserId={currentUserId} />
</Tooltip>
) : (
<Tag label={label} currentUserId={currentUserId} />
<ChangeTag label={label} currentUserId={currentUserId} />
)
}

View file

@ -1,7 +1,7 @@
import classNames from 'classnames'
import type { ReactNode } from 'react'
import type { FileOperation } from '../../services/types/file-operation'
import Badge from '../../../../shared/components/badge'
import Tag from '@/shared/components/tag'
type FileTreeItemProps = {
name: string
@ -25,14 +25,9 @@ export default function HistoryFileTreeItem({
>
{name}
</div>
{operation ? (
<Badge
bsStyle={null}
className="badge-bs3 history-file-tree-item-badge"
>
{operation}
</Badge>
) : null}
{operation && (
<Tag className="history-file-tree-item-badge">{operation}</Tag>
)}
</div>
</div>
)

View file

@ -1,12 +1,10 @@
import { Badge as BSBadge } from 'react-bootstrap-5'
import { MergeAndOverride } from '../../../../../../types/utils'
import MaterialIcon from '@/shared/components/material-icon'
type BadgeProps = MergeAndOverride<
React.ComponentProps<typeof BSBadge>,
{
prepend?: React.ReactNode
closeBtnProps?: React.ComponentProps<'button'>
}
>
@ -14,12 +12,7 @@ function Badge({ prepend, children, closeBtnProps, ...rest }: BadgeProps) {
return (
<BSBadge {...rest}>
{prepend && <span className="badge-prepend">{prepend}</span>}
{children}
{closeBtnProps && (
<button type="button" className="badge-close" {...closeBtnProps}>
<MaterialIcon className="badge-close-icon" type="close" />
</button>
)}
<span className="badge-content">{children}</span>
</BSBadge>
)
}

View file

@ -0,0 +1,42 @@
import { useTranslation } from 'react-i18next'
import { Badge } 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>,
{
prepend?: React.ReactNode
closeBtnProps?: React.ComponentProps<'button'>
}
>
function Tag({
prepend,
children,
closeBtnProps,
className,
...rest
}: TagProps) {
const { t } = useTranslation()
return (
<Badge bg="light" className={classnames('badge-tag', className)} {...rest}>
{prepend && <span className="badge-prepend">{prepend}</span>}
<span className="badge-content">{children}</span>
{closeBtnProps && (
<button
type="button"
className="badge-close"
aria-label={t('remove_tag', { tagName: children })}
{...closeBtnProps}
>
<MaterialIcon className="badge-close-icon" type="close" />
</button>
)}
</Badge>
)
}
export default Tag

View file

@ -15,7 +15,6 @@ function OLBadge(props: OLBadgeProps) {
let bs3BadgeProps: React.ComponentProps<typeof BS3Badge> = {
prepend: rest.prepend,
children: rest.children,
closeBtnProps: rest.closeBtnProps,
className: rest.className,
bsStyle: rest.bg,
}

View file

@ -0,0 +1,28 @@
import Tag from '@/features/ui/components/bootstrap-5/tag'
import BS3Tag from '@/shared/components/tag'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLTagProps = React.ComponentProps<typeof Tag> & {
bs3Props?: React.ComponentProps<typeof BS3Tag>
}
function OLTag(props: OLTagProps) {
const { bs3Props, ...rest } = props
const bs3TagProps: React.ComponentProps<typeof BS3Tag> = {
children: rest.children,
prepend: rest.prepend,
closeBtnProps: rest.closeBtnProps,
className: rest.className,
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Tag {...bs3TagProps} />}
bs5={<Tag {...rest} />}
/>
)
}
export default OLTag

View file

@ -1,28 +1,18 @@
import { Label } from 'react-bootstrap'
import classnames from 'classnames'
import { MergeAndOverride } from '../../../../types/utils'
import OLBadge from '@/features/ui/components/ol/ol-badge'
type BadgeProps = MergeAndOverride<
React.ComponentProps<'span'>,
{
prepend?: React.ReactNode
children: React.ReactNode
closeBtnProps?: React.ComponentProps<'button'>
className?: string
bsStyle?: NonNullable<
React.ComponentProps<typeof OLBadge>['bs3Props']
>['bsStyle']
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
}
>
function Badge({
prepend,
children,
closeBtnProps,
bsStyle,
className,
...rest
}: BadgeProps) {
function Badge({ prepend, children, bsStyle, className, ...rest }: BadgeProps) {
const classNames =
bsStyle === null
? className
@ -30,13 +20,8 @@ function Badge({
return (
<span className={classNames} {...rest}>
{prepend && <span className="badge-bs3-prepend">{prepend}</span>}
{prepend && <span className="badge-tag-bs3-prepend">{prepend}</span>}
{children}
{closeBtnProps && (
<button type="button" className="badge-bs3-close" {...closeBtnProps}>
<span aria-hidden="true">&times;</span>
</button>
)}
</span>
)
}

View file

@ -0,0 +1,45 @@
import { useTranslation } from 'react-i18next'
import { Label } from 'react-bootstrap'
import { MergeAndOverride } from '../../../../types/utils'
import classnames from 'classnames'
type TagProps = MergeAndOverride<
React.ComponentProps<'span'>,
{
prepend?: React.ReactNode
children: React.ReactNode
closeBtnProps?: React.ComponentProps<'button'>
className?: string
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
}
>
function Tag({
prepend,
children,
closeBtnProps,
bsStyle,
className,
...rest
}: TagProps) {
const { t } = useTranslation()
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>
{closeBtnProps && (
<button
type="button"
className="badge-tag-bs3-close"
aria-label={t('remove_tag', { tagName: children })}
{...closeBtnProps}
>
<span aria-hidden="true">&times;</span>
</button>
)}
</span>
)
}
export default Tag

View file

@ -1,11 +1,10 @@
import Badge from '@/shared/components/badge'
import BS3Badge from '@/shared/components/badge'
import Icon from '@/shared/components/icon'
import type { Meta, StoryObj } from '@storybook/react'
import classnames from 'classnames'
const meta: Meta<typeof Badge> = {
const meta: Meta<typeof BS3Badge> = {
title: 'Shared / Components / Badge / Bootstrap 3',
component: Badge,
component: BS3Badge,
parameters: {
bootstrap5: false,
},
@ -19,7 +18,7 @@ const meta: Meta<typeof Badge> = {
},
},
bsStyle: {
options: [null, 'primary', 'warning', 'danger'],
options: ['info', 'primary', 'warning', 'danger'],
control: { type: 'radio' },
},
className: {
@ -27,67 +26,34 @@ const meta: Meta<typeof Badge> = {
disable: true,
},
},
closeBtnProps: {
table: {
disable: true,
},
},
},
}
export default meta
type Story = StoryObj<typeof Badge>
type Story = StoryObj<typeof BS3Badge>
export const BadgeDefault: Story = {
render: args => {
return (
<Badge
className={classnames({ 'badge-bs3': args.bsStyle === null })}
{...args}
/>
<div className="small">
<BS3Badge {...args} />
</div>
)
},
}
BadgeDefault.args = {
bsStyle: null,
bsStyle: meta.argTypes!.bsStyle!.options[0],
}
export const BadgePrepend: Story = {
render: args => {
return (
<Badge
className={classnames({ 'badge-bs3': args.bsStyle === null })}
prepend={<Icon type="tag" fw />}
{...args}
/>
<div className="small">
<BS3Badge prepend={<Icon type="star" fw />} {...args} />
</div>
)
},
}
BadgePrepend.args = {
bsStyle: null,
}
export const BadgeWithCloseButton: Story = {
render: args => {
return (
<Badge
className={classnames({ 'badge-bs3': args.bsStyle === null })}
prepend={<Icon type="tag" fw />}
closeBtnProps={{
onClick: () => alert('Close triggered!'),
}}
{...args}
/>
)
},
}
BadgeWithCloseButton.args = {
bsStyle: null,
}
BadgeWithCloseButton.argTypes = {
bsStyle: {
table: {
disable: true,
},
},
bsStyle: meta.argTypes!.bsStyle!.options[0],
}

View file

@ -49,7 +49,7 @@ export const BadgeDefault: Story = {
},
}
BadgeDefault.args = {
bg: 'light',
bg: meta.argTypes!.bg!.options[0],
}
export const BadgePrepend: Story = {
@ -57,37 +57,12 @@ export const BadgePrepend: Story = {
return (
<Badge
className={classnames({ 'text-dark': args.bg === 'light' })}
prepend={<Icon type="tag" fw />}
prepend={<Icon type="star" fw />}
{...args}
/>
)
},
}
BadgePrepend.args = {
bg: 'light',
}
export const BadgeWithCloseButton: Story = {
render: args => {
return (
<Badge
className={classnames({ 'text-dark': args.bg === 'light' })}
prepend={<Icon type="tag" fw />}
closeBtnProps={{
onClick: () => alert('Close triggered!'),
}}
{...args}
/>
)
},
}
BadgeWithCloseButton.args = {
bg: 'light',
}
BadgeWithCloseButton.argTypes = {
bg: {
table: {
disable: true,
},
},
bg: meta.argTypes!.bg!.options[0],
}

View file

@ -0,0 +1,70 @@
import Icon from '@/shared/components/icon'
import BS3Tag from '@/shared/components/tag'
import type { Meta, StoryObj } from '@storybook/react'
const meta: Meta<typeof BS3Tag> = {
title: 'Shared / Components / Tag / Bootstrap 3',
component: BS3Tag,
parameters: {
bootstrap5: false,
},
args: {
children: 'Tag',
},
argTypes: {
prepend: {
table: {
disable: true,
},
},
className: {
table: {
disable: true,
},
},
closeBtnProps: {
table: {
disable: true,
},
},
},
}
export default meta
type Story = StoryObj<typeof BS3Tag>
export const TagDefault: Story = {
render: args => {
return (
<div className="small">
<BS3Tag {...args} />
</div>
)
},
}
export const TagPrepend: Story = {
render: args => {
return (
<div className="small">
<BS3Tag prepend={<Icon type="tag" fw />} {...args} />
</div>
)
},
}
export const TagWithCloseButton: Story = {
render: args => {
return (
<div className="small">
<BS3Tag
prepend={<Icon type="tag" fw />}
closeBtnProps={{
onClick: () => alert('Close triggered!'),
}}
{...args}
/>
</div>
)
},
}

View file

@ -0,0 +1,60 @@
import Icon from '@/shared/components/icon'
import Tag from '@/features/ui/components/bootstrap-5/tag'
import type { Meta, StoryObj } from '@storybook/react'
const meta: Meta<typeof Tag> = {
title: 'Shared / Components / Tag / Bootstrap 5',
component: Tag,
parameters: {
bootstrap5: true,
},
args: {
children: 'Tag',
},
argTypes: {
prepend: {
table: {
disable: true,
},
},
className: {
table: {
disable: true,
},
},
closeBtnProps: {
table: {
disable: true,
},
},
},
}
export default meta
type Story = StoryObj<typeof Tag>
export const TagDefault: Story = {
render: args => {
return <Tag {...args} />
},
}
export const TagPrepend: Story = {
render: args => {
return <Tag prepend={<Icon type="tag" fw />} {...args} />
},
}
export const TagWithCloseButton: Story = {
render: args => {
return (
<Tag
prepend={<Icon type="tag" fw />}
closeBtnProps={{
onClick: () => alert('Close triggered!'),
}}
{...args}
/>
)
},
}

View file

@ -1,13 +1,16 @@
.badge {
display: inline-flex;
align-items: center;
overflow: hidden;
align-items: stretch;
max-width: 100%;
line-height: var(--line-height-01);
padding: 0 var(--bs-badge-padding-x);
}
.badge-prepend {
margin-left: calc($spacing-01 / -2);
margin-right: $spacing-01;
display: flex;
align-items: center;
}
.badge-close {
@ -16,15 +19,14 @@
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
// a random number that would cause the close button to expand enough
// so that it won't be affected by badge's padding
$expand: 100px;
padding: $expand $spacing-01;
margin: (-$expand) (-$spacing-02) (-$expand) $spacing-02;
user-select: none;
padding: 0 $spacing-02;
margin: 0 (-$spacing-02) 0 $spacing-02;
border-top-right-radius: inherit;
border-bottom-right-radius: inherit;
color: inherit;
user-select: none;
.badge-close-icon {
font-size: $font-size-base;
@ -34,3 +36,17 @@
background-color: var(--neutral-40);
}
}
.badge-content {
@include text-truncate;
padding: var(--bs-badge-padding-y) 0;
}
.badge-tag {
@include body-sm;
color: $dark;
&:hover {
background-color: var(--neutral-30) !important;
}
}

View file

@ -1,10 +1,10 @@
.badge-bs3 {
.badge-tag-bs3 {
@size: 24px;
@padding: 4px;
display: inline-flex;
align-items: center;
overflow: hidden;
height: @size;
max-width: 100%;
min-height: @size;
padding: 0 @padding;
white-space: nowrap;
@ -26,10 +26,14 @@
margin-left: 4px;
font-size: 20px;
height: @size;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: -@padding;
border-top-right-radius: inherit;
border-bottom-right-radius: inherit;
color: inherit;
&:hover {
@ -37,13 +41,7 @@
}
}
&-sm {
@size-sm: 20px;
height: @size-sm;
font-size: @font-size-extra-small;
.badge-bs3-close {
width: @size-sm;
font-size: @size-sm;
}
&-content {
.text-overflow();
}
}